server_test.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. package server
  2. import (
  3. "context"
  4. "errors"
  5. "net/http"
  6. "net/http/httptest"
  7. "sync/atomic"
  8. "testing"
  9. "time"
  10. "github.com/imgproxy/imgproxy/v3/errorreport"
  11. "github.com/imgproxy/imgproxy/v3/httpheaders"
  12. "github.com/imgproxy/imgproxy/v3/monitoring"
  13. "github.com/stretchr/testify/suite"
  14. )
  15. type ServerTestSuite struct {
  16. suite.Suite
  17. config *Config
  18. monitoring *monitoring.Monitoring
  19. errorReporter *errorreport.Reporter
  20. blankRouter *Router
  21. }
  22. func (s *ServerTestSuite) SetupTest() {
  23. c := NewDefaultConfig()
  24. s.config = &c
  25. s.config.Bind = "127.0.0.1:0" // Use port 0 for auto-assignment
  26. mc := monitoring.NewDefaultConfig()
  27. m, err := monitoring.New(s.T().Context(), &mc, 1)
  28. s.Require().NoError(err)
  29. s.monitoring = m
  30. erCfg := errorreport.NewDefaultConfig()
  31. er, err := errorreport.New(&erCfg)
  32. s.Require().NoError(err)
  33. s.errorReporter = er
  34. r, err := NewRouter(s.config, m, er)
  35. s.Require().NoError(err)
  36. s.blankRouter = r
  37. }
  38. func (s *ServerTestSuite) mockHandler(reqID string, rw ResponseWriter, r *http.Request) error {
  39. return nil
  40. }
  41. func (s *ServerTestSuite) wrapRW(rw http.ResponseWriter) ResponseWriter {
  42. return s.blankRouter.rwFactory.NewWriter(rw)
  43. }
  44. func (s *ServerTestSuite) TestStartServerWithInvalidBind() {
  45. ctx, cancel := context.WithCancel(s.T().Context())
  46. // Track if cancel was called using atomic
  47. var cancelCalled atomic.Bool
  48. cancelWrapper := func() {
  49. cancel()
  50. cancelCalled.Store(true)
  51. }
  52. invalidConfig := NewDefaultConfig()
  53. invalidConfig.Bind = "-1.-1.-1.-1" // Invalid address
  54. r, err := NewRouter(&invalidConfig, s.monitoring, s.errorReporter)
  55. s.Require().NoError(err)
  56. server, err := Start(cancelWrapper, r)
  57. s.Require().Error(err)
  58. s.Nil(server)
  59. s.Contains(err.Error(), "can't start server")
  60. // Check if cancel was called using Eventually
  61. s.Require().Eventually(cancelCalled.Load, 100*time.Millisecond, 10*time.Millisecond)
  62. // Also verify the context was cancelled
  63. s.Require().Eventually(func() bool {
  64. select {
  65. case <-ctx.Done():
  66. return true
  67. default:
  68. return false
  69. }
  70. }, 100*time.Millisecond, 10*time.Millisecond)
  71. }
  72. func (s *ServerTestSuite) TestShutdown() {
  73. _, cancel := context.WithCancel(context.Background())
  74. defer cancel()
  75. server, err := Start(cancel, s.blankRouter)
  76. s.Require().NoError(err)
  77. s.NotNil(server)
  78. // Test graceful shutdown
  79. shutdownCtx, shutdownCancel := context.WithTimeout(s.T().Context(), 10*time.Second)
  80. defer shutdownCancel()
  81. // Should not panic or hang
  82. s.NotPanics(func() {
  83. server.Shutdown(shutdownCtx)
  84. })
  85. }
  86. func (s *ServerTestSuite) TestWithCORS() {
  87. tests := []struct {
  88. name string
  89. corsAllowOrigin string
  90. expectedOrigin string
  91. expectedMethods string
  92. }{
  93. {
  94. name: "WithCORSOrigin",
  95. corsAllowOrigin: "https://example.com",
  96. expectedOrigin: "https://example.com",
  97. expectedMethods: "GET, OPTIONS",
  98. },
  99. {
  100. name: "NoCORSOrigin",
  101. corsAllowOrigin: "",
  102. expectedOrigin: "",
  103. expectedMethods: "",
  104. },
  105. }
  106. for _, tt := range tests {
  107. s.Run(tt.name, func() {
  108. config := NewDefaultConfig()
  109. config.CORSAllowOrigin = tt.corsAllowOrigin
  110. router, err := NewRouter(&config, s.monitoring, s.errorReporter)
  111. s.Require().NoError(err)
  112. wrappedHandler := router.WithCORS(s.mockHandler)
  113. req := httptest.NewRequest("GET", "/test", nil)
  114. rw := httptest.NewRecorder()
  115. wrappedHandler("test-req-id", s.wrapRW(rw), req)
  116. s.Equal(tt.expectedOrigin, rw.Header().Get(httpheaders.AccessControlAllowOrigin))
  117. s.Equal(tt.expectedMethods, rw.Header().Get(httpheaders.AccessControlAllowMethods))
  118. })
  119. }
  120. }
  121. func (s *ServerTestSuite) TestWithSecret() {
  122. tests := []struct {
  123. name string
  124. secret string
  125. authHeader string
  126. expectError bool
  127. }{
  128. {
  129. name: "ValidSecret",
  130. secret: "test-secret",
  131. authHeader: "Bearer test-secret",
  132. },
  133. {
  134. name: "InvalidSecret",
  135. secret: "foo-secret",
  136. authHeader: "Bearer wrong-secret",
  137. expectError: true,
  138. },
  139. {
  140. name: "NoSecretConfigured",
  141. secret: "",
  142. authHeader: "",
  143. },
  144. }
  145. for _, tt := range tests {
  146. s.Run(tt.name, func() {
  147. config := NewDefaultConfig()
  148. config.Secret = tt.secret
  149. router, err := NewRouter(&config, s.monitoring, s.errorReporter)
  150. s.Require().NoError(err)
  151. wrappedHandler := router.WithSecret(s.mockHandler)
  152. req := httptest.NewRequest("GET", "/test", nil)
  153. if tt.authHeader != "" {
  154. req.Header.Set(httpheaders.Authorization, tt.authHeader)
  155. }
  156. rw := httptest.NewRecorder()
  157. err = wrappedHandler("test-req-id", s.wrapRW(rw), req)
  158. if tt.expectError {
  159. s.Require().Error(err)
  160. } else {
  161. s.Require().NoError(err)
  162. }
  163. })
  164. }
  165. }
  166. func (s *ServerTestSuite) TestIntoSuccess() {
  167. mockHandler := func(reqID string, rw ResponseWriter, r *http.Request) error {
  168. rw.WriteHeader(http.StatusOK)
  169. return nil
  170. }
  171. wrappedHandler := s.blankRouter.WithReportError(mockHandler)
  172. req := httptest.NewRequest("GET", "/test", nil)
  173. rw := httptest.NewRecorder()
  174. wrappedHandler("test-req-id", s.wrapRW(rw), req)
  175. s.Equal(http.StatusOK, rw.Code)
  176. }
  177. func (s *ServerTestSuite) TestIntoWithError() {
  178. testError := errors.New("test error")
  179. mockHandler := func(reqID string, rw ResponseWriter, r *http.Request) error {
  180. return testError
  181. }
  182. wrappedHandler := s.blankRouter.WithReportError(mockHandler)
  183. req := httptest.NewRequest("GET", "/test", nil)
  184. rw := httptest.NewRecorder()
  185. wrappedHandler("test-req-id", s.wrapRW(rw), req)
  186. s.Equal(http.StatusInternalServerError, rw.Code)
  187. s.Equal("text/plain", rw.Header().Get(httpheaders.ContentType))
  188. }
  189. func (s *ServerTestSuite) TestIntoPanicWithError() {
  190. testError := errors.New("panic error")
  191. mockHandler := func(reqID string, rw ResponseWriter, r *http.Request) error {
  192. panic(testError)
  193. }
  194. wrappedHandler := s.blankRouter.WithPanic(mockHandler)
  195. req := httptest.NewRequest("GET", "/test", nil)
  196. rw := httptest.NewRecorder()
  197. s.NotPanics(func() {
  198. err := wrappedHandler("test-req-id", s.wrapRW(rw), req)
  199. s.Require().Error(err, "panic error")
  200. })
  201. s.Equal(http.StatusOK, rw.Code)
  202. }
  203. func (s *ServerTestSuite) TestIntoPanicWithAbortHandler() {
  204. mockHandler := func(reqID string, rw ResponseWriter, r *http.Request) error {
  205. panic(http.ErrAbortHandler)
  206. }
  207. wrappedHandler := s.blankRouter.WithPanic(mockHandler)
  208. req := httptest.NewRequest("GET", "/test", nil)
  209. rw := httptest.NewRecorder()
  210. // Should re-panic with ErrAbortHandler
  211. s.Panics(func() {
  212. wrappedHandler("test-req-id", s.wrapRW(rw), req)
  213. })
  214. }
  215. func (s *ServerTestSuite) TestIntoPanicWithNonError() {
  216. mockHandler := func(reqID string, rw ResponseWriter, r *http.Request) error {
  217. panic("string panic")
  218. }
  219. wrappedHandler := s.blankRouter.WithPanic(mockHandler)
  220. req := httptest.NewRequest("GET", "/test", nil)
  221. rw := httptest.NewRecorder()
  222. // Should re-panic with non-error panics
  223. s.NotPanics(func() {
  224. err := wrappedHandler("test-req-id", s.wrapRW(rw), req)
  225. s.Require().Error(err, "string panic")
  226. })
  227. }
  228. func TestServerTestSuite(t *testing.T) {
  229. suite.Run(t, new(ServerTestSuite))
  230. }