package routing import ( "bytes" "fmt" "io" "os" "strconv" "strings" "time" "math/rand" "encoding/json" "testing" "net/http" ht "net/http/httptest" f "github.com/gofiber/fiber/v2" "github.com/stretchr/testify/assert" "webservice/configuration" "webservice/state" ) func setup() ( *f.App, *configuration.Config, state.Store, *bool ){ os.Setenv( "ENV_NAME", "testing" ) config, _ := configuration.New() server := f.New( f.Config{ AppName: "test", DisableStartupMessage: false, BodyLimit: configuration.BODY_SIZE_LIMIT, }) store := state.NewEphemeralStore() var isHealthy = true _ = SetRoutes( server, config, store, &isHealthy ) return server, config, store, &isHealthy } func bodyToString( body *io.ReadCloser ) ( string, error ) { defer ( *body ).Close() bodyBytes, err := io.ReadAll( *body ) if err != nil { return "", err } return string( bodyBytes ), nil } func jsonToMap( body *io.ReadCloser ) ( map[string]interface{}, error ) { defer ( *body ).Close() var data map[string]interface{} bodyBytes, err := io.ReadAll( *body ) if err != nil { return data, err } if err := json.Unmarshal( bodyBytes, &data ); err != nil { return data, err } return data, nil } func jsonToStringSlice( body *io.ReadCloser ) ( []string, error ) { defer ( *body ).Close() var data []string bodyBytes, err := io.ReadAll( *body ) if err != nil { return data, err } if err := json.Unmarshal( bodyBytes, &data ); err != nil { return data, err } return data, nil } func generateRandomNumberString() string { r := rand.New( rand.NewSource( time.Now().Unix() ) ) randomNumber := r.Int63() return fmt.Sprintf( "%d", randomNumber ) } func generateRandomBytes( length int ) []byte { r := rand.New( rand.NewSource( time.Now().Unix() ) ) bytes := make( []byte, length ) _, _ = r.Read( bytes ) return bytes } func TestIndexRoute( t *testing.T ){ router, _, _, _ := setup() req := ht.NewRequest( "GET", "/", nil ) req.Header.Add( "Accept", "text/html" ) res, _ := router.Test( req, -1 ) bodyContent, err := bodyToString( &res.Body ) assert.Nil( t, err ) assert.Contains( t, bodyContent, "</html>" ) assert.Contains( t, bodyContent, "<head>" ) req = ht.NewRequest( "GET", "/", nil ) res, _ = router.Test( req, -1 ) bodyContent, err = bodyToString( &res.Body ) assert.Nil( t, err ) assert.Equal( t, "Hello, World!", bodyContent ) } func TestHealthRoute( t *testing.T ){ router, _, _, healthiness := setup() req := ht.NewRequest( "GET", "/health", nil ) res, _ := router.Test( req, -1 ) bodyContent, err := jsonToMap( &res.Body ) status := bodyContent[ "status" ].( string ) assert.Equal( t, http.StatusOK, res.StatusCode ) assert.Nil( t, err ) assert.Equal( t, "pass", status ) *healthiness = false req = ht.NewRequest( "GET", "/health", nil ) res, _ = router.Test( req, -1 ) bodyContent, err = jsonToMap( &res.Body ) status = bodyContent[ "status" ].( string ) assert.Equal( t, http.StatusServiceUnavailable, res.StatusCode ) assert.Nil( t, err ) assert.Equal( t, "fail", status ) } func TestEnvRoute( t *testing.T ){ router, config, _, _ := setup() envVarName := "TEST_ENV_VAR" envVarValue := generateRandomNumberString() os.Setenv( envVarName, envVarValue ) req := ht.NewRequest( "GET", "/env", nil ) res, _ := router.Test( req, -1 ) bodyContent, err := bodyToString( &res.Body ) assert.Equal( t, http.StatusOK, res.StatusCode ) assert.Nil( t, err ) assert.Contains( t, bodyContent, fmt.Sprintf( "%s=%s", envVarName, envVarValue ) ) ( *config ).Environment = "production" req = ht.NewRequest( "GET", "/env", nil ) res, _ = router.Test( req, -1 ) assert.Equal( t, http.StatusForbidden, res.StatusCode ) } func TestState( t *testing.T ){ router, _, store, _ := setup() const statePath1 = "/state/just-a-test" const statePath1Mime = "text/plain" const statePath1Body1 = "just a test body" const statePath1Body2 = "this body just changed" const statePath2 = "/state/another-test" const statePath2Mime = "application/octet-stream" const statePath2BodySize = 128 statePath2Body := generateRandomBytes( statePath2BodySize ) req := ht.NewRequest( "GET", statePath1, nil ) res, _ := router.Test( req, -1 ) assert.Equal( t, http.StatusNotFound, res.StatusCode ) req = ht.NewRequest( "PUT", statePath1, nil ) req.Header.Add( "Content-Type", "not a MIME type" ) res, _ = router.Test( req, -1 ) assert.Equal( t, http.StatusBadRequest, res.StatusCode ) req = ht.NewRequest( "PUT", statePath1, strings.NewReader( statePath1Body1 ) ) req.Header.Add( "Content-Type", statePath1Mime ) res, _ = router.Test( req, -1 ) assert.Equal( t, http.StatusCreated, res.StatusCode ) req = ht.NewRequest( "PUT", statePath1, strings.NewReader( statePath1Body1 ) ) req.Header.Add( "Content-Type", statePath1Mime ) res, _ = router.Test( req, -1 ) assert.Equal( t, http.StatusOK, res.StatusCode ) req = ht.NewRequest( "PUT", statePath1, strings.NewReader( statePath1Body2 ) ) req.Header.Add( "Content-Type", statePath1Mime ) res, _ = router.Test( req, -1 ) assert.Equal( t, http.StatusNoContent, res.StatusCode ) req = ht.NewRequest( "GET", statePath1, nil ) res, _ = router.Test( req, -1 ) bodyContent, err := bodyToString( &res.Body ) assert.Nil( t, err ) assert.Equal( t, http.StatusOK, res.StatusCode ) assert.Equal( t, statePath1Mime, res.Header[ "Content-Type" ][0] ) assert.Equal( t, statePath1Body2, bodyContent ) req = ht.NewRequest( "DELETE", statePath2, nil ) res, _ = router.Test( req, -1 ) assert.Equal( t, http.StatusNotFound, res.StatusCode ) req = ht.NewRequest( "PUT", statePath2, bytes.NewReader( statePath2Body ) ) req.Header.Add( "Content-Type", statePath2Mime ) res, _ = router.Test( req, -1 ) assert.Equal( t, http.StatusCreated, res.StatusCode ) req = ht.NewRequest( "HEAD", statePath2, nil ) res, _ = router.Test( req, -1 ) contentLength, err := strconv.ParseInt( res.Header[ "Content-Length" ][0], 10, 64 ) assert.Nil( t, err ) assert.Equal( t, http.StatusOK, res.StatusCode ) assert.Equal( t, statePath2Mime, res.Header[ "Content-Type" ][0] ) assert.Equal( t, int64( statePath2BodySize ), contentLength ) assert.IsType( t, res.Body, http.NoBody ) req = ht.NewRequest( "GET", statePath2, nil ) res, _ = router.Test( req, -1 ) bodyBytes, err := io.ReadAll( res.Body ) assert.Equal( t, http.StatusOK, res.StatusCode ) assert.Equal( t, statePath2Mime, res.Header[ "Content-Type" ][0] ) assert.Equal( t, statePath2Body, bodyBytes ) req = ht.NewRequest( "GET", "/states", nil ) req.Header.Add( "Accept", "application/json" ) res, _ = router.Test( req, -1 ) states, err := jsonToStringSlice( &res.Body ) assert.Nil( t, err ) assert.Len( t, states, 2 ) assert.Contains( t, states, statePath1 ) assert.Contains( t, states, statePath2 ) req = ht.NewRequest( "DELETE", statePath1, nil ) res, _ = router.Test( req, -1 ) assert.Equal( t, http.StatusNoContent, res.StatusCode ) req = ht.NewRequest( "GET", "/states", nil ) res, _ = router.Test( req, -1 ) statesPlain, err := bodyToString( &res.Body ) assert.Nil( t, err ) assert.NotContains( t, statesPlain, statePath1 ) assert.Contains( t, statesPlain, statePath2 ) err = store.Disconnect() req = ht.NewRequest( "GET", statePath2, nil ) res, _ = router.Test( req, -1 ) assert.Nil( t, err ) assert.Equal( t, http.StatusInternalServerError, res.StatusCode ) }