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  }