goyave.dev/goyave/v4@v4.4.11/goyave_test.go (about) 1 package goyave 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "net" 8 "net/http" 9 "os" 10 "runtime" 11 "syscall" 12 "testing" 13 "time" 14 15 "goyave.dev/goyave/v4/config" 16 "goyave.dev/goyave/v4/util/fsutil" 17 18 _ "goyave.dev/goyave/v4/database/dialect/mysql" 19 ) 20 21 type GoyaveTestSuite struct { 22 TestSuite 23 } 24 25 func helloHandler(response *Response, _ *Request) { 26 response.String(http.StatusOK, "Hi!") 27 } 28 29 func (suite *GoyaveTestSuite) SetupSuite() { 30 os.Setenv("GOYAVE_ENV", "test") 31 suite.SetTimeout(5 * time.Second) 32 } 33 34 func (suite *GoyaveTestSuite) loadConfig() { 35 if err := config.Load(); err != nil { 36 suite.FailNow(err.Error()) 37 } 38 config.Set("server.tls.key", "resources/server.key") 39 config.Set("server.tls.cert", "resources/server.crt") 40 } 41 42 func (suite *GoyaveTestSuite) TestGetHost() { 43 suite.loadConfig() 44 suite.Equal("127.0.0.1:1235", getHost("http")) 45 suite.Equal("127.0.0.1:1236", getHost("https")) 46 } 47 48 func (suite *GoyaveTestSuite) TestGetAddress() { 49 suite.loadConfig() 50 suite.Equal("http://127.0.0.1:1235", getAddress("http")) 51 suite.Equal("https://127.0.0.1:1236", getAddress("https")) 52 53 config.Set("server.domain", "test.system-glitch.me") 54 suite.Equal("http://test.system-glitch.me:1235", getAddress("http")) 55 suite.Equal("https://test.system-glitch.me:1236", getAddress("https")) 56 57 config.Set("server.port", 80) 58 config.Set("server.httpsPort", 443) 59 suite.Equal("http://test.system-glitch.me", getAddress("http")) 60 suite.Equal("https://test.system-glitch.me", getAddress("https")) 61 62 suite.Equal(getAddress("http"), BaseURL()) 63 64 config.Set("server.domain", "") 65 config.Set("server.host", "0.0.0.0") 66 config.Set("server.port", 1235) 67 config.Set("server.httpsPort", 1236) 68 suite.Equal("http://127.0.0.1:1235", getAddress("http")) 69 suite.Equal("https://127.0.0.1:1236", getAddress("https")) 70 71 // Clear cached protocol value 72 // Should be loaded if not already 73 protocol = "" 74 suite.Equal(getAddress("http"), BaseURL()) 75 } 76 77 func (suite *GoyaveTestSuite) TestProxyBaseURL() { 78 suite.loadConfig() 79 80 suite.Equal(BaseURL(), ProxyBaseURL()) 81 config.Set("server.proxy.host", "127.0.0.1") 82 config.Set("server.proxy.port", 1235) 83 suite.Equal("http://127.0.0.1:1235", ProxyBaseURL()) 84 config.Set("server.proxy.protocol", "https") 85 suite.Equal("https://127.0.0.1:1235", ProxyBaseURL()) 86 87 config.Set("server.proxy.protocol", "http") 88 config.Set("server.proxy.host", "test.system-glitch.me") 89 suite.Equal("http://test.system-glitch.me:1235", ProxyBaseURL()) 90 config.Set("server.proxy.protocol", "https") 91 suite.Equal("https://test.system-glitch.me:1235", ProxyBaseURL()) 92 93 config.Set("server.proxy.protocol", "http") 94 config.Set("server.proxy.port", 80) 95 suite.Equal("http://test.system-glitch.me", ProxyBaseURL()) 96 97 config.Set("server.proxy.protocol", "https") 98 config.Set("server.proxy.port", 443) 99 suite.Equal("https://test.system-glitch.me", ProxyBaseURL()) 100 config.Set("server.proxy.base", "/baseurl") 101 suite.Equal("https://test.system-glitch.me/baseurl", ProxyBaseURL()) 102 } 103 104 func (suite *GoyaveTestSuite) TestStartStopServer() { 105 config.Clear() 106 proc, err := os.FindProcess(os.Getpid()) 107 if err == nil { 108 c := make(chan struct{}, 1) 109 c2 := make(chan struct{}, 1) 110 ctx, cancel := context.WithTimeout(context.Background(), suite.Timeout()) 111 defer cancel() 112 113 RegisterStartupHook(func() { 114 suite.True(IsReady()) 115 if runtime.GOOS == "windows" { 116 fmt.Println("Testing on a windows machine. Cannot test proc signals") 117 Stop() 118 } else { 119 time.Sleep(10 * time.Millisecond) 120 if err := proc.Signal(syscall.SIGTERM); err != nil { 121 suite.Fail(err.Error()) 122 } 123 } 124 c <- struct{}{} 125 }) 126 defer ClearStartupHooks() 127 go func() { 128 if err := Start(func(router *Router) {}); err != nil { 129 suite.Fail(err.Error()) 130 } 131 c2 <- struct{}{} 132 }() 133 134 select { 135 case <-ctx.Done(): 136 suite.Fail(fmt.Sprintf("Timeout (%dms) exceeded in server start/stop test", suite.Timeout().Milliseconds())) 137 case <-c2: 138 suite.False(IsReady()) 139 suite.Nil(server) 140 <-c 141 } 142 } else { 143 suite.Fail("Couldn't get process PID, skipping SIGINT test") 144 } 145 } 146 147 func (suite *GoyaveTestSuite) TestTLSServer() { 148 suite.loadConfig() 149 protocol = "https" 150 config.Set("server.protocol", "https") 151 suite.RunServer(func(router *Router) { 152 router.Route("GET", "/hello", helloHandler) 153 }, func() { 154 netClient := suite.getHTTPClient() 155 resp, err := netClient.Get("http://127.0.0.1:1235/hello") 156 suite.Nil(err) 157 if err != nil { 158 fmt.Println(err) 159 } 160 161 suite.NotNil(resp) 162 if resp != nil { 163 suite.Equal(308, resp.StatusCode) 164 165 body, err := io.ReadAll(resp.Body) 166 resp.Body.Close() 167 suite.Nil(err) 168 suite.Equal("<a href=\"https://127.0.0.1:1236/hello\">Permanent Redirect</a>.\n\n", string(body)) 169 } 170 171 resp, err = netClient.Get("http://127.0.0.1:1235/hello?param=1") 172 suite.Nil(err) 173 if err != nil { 174 fmt.Println(err) 175 } 176 177 suite.NotNil(resp) 178 if resp != nil { 179 suite.Equal(308, resp.StatusCode) 180 181 body, err := io.ReadAll(resp.Body) 182 resp.Body.Close() 183 suite.Nil(err) 184 suite.Equal("<a href=\"https://127.0.0.1:1236/hello?param=1\">Permanent Redirect</a>.\n\n", string(body)) 185 } 186 187 resp, err = netClient.Get("https://127.0.0.1:1236/hello") 188 suite.Nil(err) 189 if err != nil { 190 fmt.Println(err) 191 } 192 193 suite.NotNil(resp) 194 if resp != nil { 195 suite.Equal(200, resp.StatusCode) 196 197 body, err := io.ReadAll(resp.Body) 198 resp.Body.Close() 199 suite.Nil(err) 200 suite.Equal("Hi!", string(body)) 201 } 202 }) 203 204 config.Set("server.protocol", "http") 205 protocol = "http" 206 } 207 208 func (suite *GoyaveTestSuite) TestTLSRedirectServerError() { 209 suite.loadConfig() 210 c := make(chan bool) 211 c2 := make(chan bool) 212 ctx, cancel := context.WithTimeout(context.Background(), suite.Timeout()) 213 defer cancel() 214 215 go func() { 216 go func() { 217 // Run a server using the same port. 218 ln, err := net.Listen("tcp", getHost("http")) 219 if err != nil { 220 suite.Fail(err.Error()) 221 return 222 } 223 c2 <- true 224 <-c2 225 ln.Close() 226 c2 <- true 227 }() 228 <-c2 229 config.Set("server.protocol", "https") 230 protocol = "https" 231 suite.RunServer(func(router *Router) {}, func() {}) 232 config.Set("server.protocol", "http") 233 protocol = "http" 234 c2 <- true 235 <-c2 236 c <- true 237 }() 238 239 select { 240 case <-ctx.Done(): 241 suite.Fail("Timeout exceeded in redirect server error test") 242 case <-c: 243 suite.False(IsReady()) 244 suite.Nil(redirectServer) 245 } 246 } 247 248 func (suite *GoyaveTestSuite) TestStaticServing() { 249 suite.RunServer(func(router *Router) { 250 router.Static("/resources", "resources", true) 251 }, func() { 252 netClient := suite.getHTTPClient() 253 resp, err := netClient.Get("http://127.0.0.1:1235/resources/nothing") 254 suite.Nil(err) 255 if err != nil { 256 fmt.Println(err) 257 } 258 suite.NotNil(resp) 259 if resp != nil { 260 suite.Equal(404, resp.StatusCode) 261 resp.Body.Close() 262 } 263 264 err = os.WriteFile("resources/template/test-static-serve.txt", []byte("test-content"), 0644) 265 if err != nil { 266 panic(err) 267 } 268 defer fsutil.Delete("resources/template/test-static-serve.txt") 269 resp, err = netClient.Get("http://127.0.0.1:1235/resources/template/test-static-serve.txt") 270 suite.Nil(err) 271 if err != nil { 272 fmt.Println(err) 273 } 274 suite.NotNil(resp) 275 if resp != nil { 276 suite.Equal(200, resp.StatusCode) 277 278 body, err := io.ReadAll(resp.Body) 279 resp.Body.Close() 280 suite.Nil(err) 281 suite.Equal("test-content", string(body)) 282 } 283 }) 284 } 285 286 func (suite *GoyaveTestSuite) TestServerError() { 287 suite.loadConfig() 288 suite.testServerError("http") 289 suite.testServerError("https") 290 } 291 292 func (suite *GoyaveTestSuite) testServerError(proto string) { 293 c := make(chan error) 294 c2 := make(chan bool) 295 ctx, cancel := context.WithTimeout(context.Background(), suite.Timeout()) 296 defer cancel() 297 298 var ln net.Listener 299 300 go func() { 301 go func() { 302 303 // Run a server using the same port as Goyave, so Goyave fails to bind. 304 if proto != "https" { 305 var err error 306 ln, err = net.Listen("tcp", getHost(proto)) 307 if err != nil { 308 suite.Fail(err.Error()) 309 } 310 c2 <- true 311 } else { 312 c2 <- true 313 } 314 c2 <- true 315 }() 316 <-c2 317 config.Set("server.protocol", proto) 318 protocol = proto 319 if proto == "https" { 320 // Invalid certificates 321 config.Set("server.tls.key", "doesntexist") 322 config.Set("server.tls.cert", "doesntexist") 323 } 324 325 err := Start(func(router *Router) {}) 326 config.Set("server.protocol", "http") 327 protocol = "http" 328 c <- err 329 }() 330 331 select { 332 case <-ctx.Done(): 333 suite.Fail("Timeout exceeded in server error test") 334 case err := <-c: 335 suite.False(IsReady()) 336 suite.Nil(server) 337 suite.NotNil(err) 338 if proto == "https" { 339 suite.Equal(ExitHTTPError, err.(*Error).ExitCode) 340 } else { 341 suite.Equal(ExitNetworkError, err.(*Error).ExitCode) 342 } 343 } 344 345 if proto != "https" { 346 ln.Close() 347 } 348 <-c2 349 } 350 351 func (suite *GoyaveTestSuite) TestServerAlreadyRunning() { 352 suite.loadConfig() 353 suite.RunServer(func(router *Router) {}, func() { 354 suite.Panics(func() { 355 if err := Start(func(router *Router) {}); err != nil { 356 suite.Fail(err.Error()) 357 } 358 }) 359 }) 360 } 361 362 func (suite *GoyaveTestSuite) TestMaintenanceMode() { 363 suite.loadConfig() 364 suite.RunServer(func(router *Router) { 365 router.Route("GET", "/hello", helloHandler) 366 }, func() { 367 EnableMaintenance() 368 suite.True(IsMaintenanceEnabled()) 369 370 netClient := suite.getHTTPClient() 371 resp, err := netClient.Get("http://127.0.0.1:1235/hello") 372 suite.Nil(err) 373 if err != nil { 374 fmt.Println(err) 375 } 376 377 suite.NotNil(resp) 378 if resp != nil { 379 suite.Equal(503, resp.StatusCode) 380 resp.Body.Close() 381 } 382 383 DisableMaintenance() 384 suite.False(IsMaintenanceEnabled()) 385 386 resp, err = netClient.Get("http://127.0.0.1:1235/hello") 387 suite.Nil(err) 388 if err != nil { 389 fmt.Println(err) 390 } 391 392 suite.NotNil(resp) 393 if resp != nil { 394 suite.Equal(200, resp.StatusCode) 395 396 body, err := io.ReadAll(resp.Body) 397 resp.Body.Close() 398 suite.Nil(err) 399 suite.Equal("Hi!", string(body)) 400 } 401 }) 402 403 config.Set("server.maintenance", true) 404 suite.RunServer(func(router *Router) { 405 router.Route("GET", "/hello", helloHandler) 406 }, func() { 407 suite.True(IsMaintenanceEnabled()) 408 409 netClient := suite.getHTTPClient() 410 resp, err := netClient.Get("http://127.0.0.1:1235/hello") 411 suite.Nil(err) 412 if err != nil { 413 fmt.Println(err) 414 } 415 416 suite.NotNil(resp) 417 if resp != nil { 418 suite.Equal(503, resp.StatusCode) 419 resp.Body.Close() 420 } 421 422 DisableMaintenance() 423 424 suite.False(IsMaintenanceEnabled()) 425 426 resp, err = netClient.Get("http://127.0.0.1:1235/hello") 427 suite.Nil(err) 428 if err != nil { 429 fmt.Println(err) 430 } 431 432 suite.NotNil(resp) 433 if resp != nil { 434 suite.Equal(200, resp.StatusCode) 435 436 body, err := io.ReadAll(resp.Body) 437 resp.Body.Close() 438 suite.Nil(err) 439 suite.Equal("Hi!", string(body)) 440 } 441 }) 442 config.Set("server.maintenance", false) 443 } 444 445 func (suite *GoyaveTestSuite) TestAutoMigrate() { 446 suite.loadConfig() 447 config.Set("database.connection", "mysql") 448 config.Set("database.autoMigrate", true) 449 suite.RunServer(func(router *Router) {}, func() {}) 450 config.Set("database.autoMigrate", false) 451 config.Set("database.Connection", "none") 452 } 453 454 func (suite *GoyaveTestSuite) TestError() { 455 err := &Error{fmt.Errorf("test error"), ExitHTTPError} 456 suite.Equal("test error", err.Error()) 457 } 458 459 func (suite *GoyaveTestSuite) TestConfigError() { 460 config.Clear() 461 if err := os.Chdir("config"); err != nil { 462 panic(err) 463 } 464 defer os.Chdir("..") 465 466 os.Setenv("GOYAVE_ENV", "test_invalid") 467 defer os.Setenv("GOYAVE_ENV", "test") 468 469 c := make(chan error, 1) 470 ctx, cancel := context.WithTimeout(context.Background(), suite.Timeout()) 471 defer cancel() 472 473 go func() { 474 c <- Start(func(r *Router) {}) 475 }() 476 477 select { 478 case <-ctx.Done(): 479 suite.Fail("Timeout exceeded in Goyave test suite TestConfigError") 480 case err := <-c: 481 suite.NotNil(err) 482 if err != nil { 483 e := err.(*Error) 484 suite.Equal(ExitInvalidConfig, e.ExitCode) 485 suite.Equal("Invalid config:\n\t- \"app.environment\" type must be string", e.Error()) 486 } 487 } 488 } 489 490 func (suite *GoyaveTestSuite) TestShutdownHook() { 491 executed := false 492 RegisterShutdownHook(func() { 493 executed = true 494 }) 495 suite.Len(shutdownHooks, 1) 496 497 suite.RunServer(func(r *Router) {}, func() {}) 498 suite.True(executed) 499 500 ClearShutdownHooks() 501 suite.Len(shutdownHooks, 0) 502 } 503 504 func TestGoyaveTestSuite(t *testing.T) { 505 RunTest(t, new(GoyaveTestSuite)) 506 }