package session import ( "testing" "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/internal/storage/memory" "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" ) // go test -run Test_Session func Test_Session(t *testing.T) { t.Parallel() // session store store := New() // fiber instance app := fiber.New() // fiber context ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(ctx) // set session ctx.Request().Header.SetCookie(store.sessionName, "123") // get session sess, err := store.Get(ctx) require.NoError(t, err) require.True(t, sess.Fresh()) // get keys keys := sess.Keys() require.Equal(t, []string{}, keys) // get value name := sess.Get("name") require.Nil(t, name) // set value sess.Set("name", "john") // get value name = sess.Get("name") require.Equal(t, "john", name) keys = sess.Keys() require.Equal(t, []string{"name"}, keys) // delete key sess.Delete("name") // get value name = sess.Get("name") require.Nil(t, name) // get keys keys = sess.Keys() require.Equal(t, []string{}, keys) // get id id := sess.ID() require.Equal(t, "123", id) // save the old session first err = sess.Save() require.NoError(t, err) // requesting entirely new context to prevent falsy tests ctx = app.AcquireCtx(&fasthttp.RequestCtx{}) sess, err = store.Get(ctx) require.NoError(t, err) require.True(t, sess.Fresh()) // this id should be randomly generated as session key was deleted require.Len(t, sess.ID(), 36) // when we use the original session for the second time // the session be should be same if the session is not expired app.ReleaseCtx(ctx) ctx = app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(ctx) // request the server with the old session ctx.Request().Header.SetCookie(store.sessionName, id) sess, err = store.Get(ctx) require.NoError(t, err) require.False(t, sess.Fresh()) require.Equal(t, sess.id, id) } // go test -run Test_Session_Types func Test_Session_Types(t *testing.T) { t.Parallel() // session store store := New() // fiber instance app := fiber.New() // fiber context ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(ctx) // set cookie ctx.Request().Header.SetCookie(store.sessionName, "123") // get session sess, err := store.Get(ctx) require.NoError(t, err) require.True(t, sess.Fresh()) // the session string is no longer be 123 newSessionIDString := sess.ID() ctx.Request().Header.SetCookie(store.sessionName, newSessionIDString) type User struct { Name string } store.RegisterType(User{}) vuser := User{ Name: "John", } // set value var ( vbool = true vstring = "str" vint = 13 vint8 int8 = 13 vint16 int16 = 13 vint32 int32 = 13 vint64 int64 = 13 vuint uint = 13 vuint8 uint8 = 13 vuint16 uint16 = 13 vuint32 uint32 = 13 vuint64 uint64 = 13 vuintptr uintptr = 13 vbyte byte = 'k' vrune = 'k' vfloat32 float32 = 13 vfloat64 float64 = 13 vcomplex64 complex64 = 13 vcomplex128 complex128 = 13 ) sess.Set("vuser", vuser) sess.Set("vbool", vbool) sess.Set("vstring", vstring) sess.Set("vint", vint) sess.Set("vint8", vint8) sess.Set("vint16", vint16) sess.Set("vint32", vint32) sess.Set("vint64", vint64) sess.Set("vuint", vuint) sess.Set("vuint8", vuint8) sess.Set("vuint16", vuint16) sess.Set("vuint32", vuint32) sess.Set("vuint32", vuint32) sess.Set("vuint64", vuint64) sess.Set("vuintptr", vuintptr) sess.Set("vbyte", vbyte) sess.Set("vrune", vrune) sess.Set("vfloat32", vfloat32) sess.Set("vfloat64", vfloat64) sess.Set("vcomplex64", vcomplex64) sess.Set("vcomplex128", vcomplex128) // save session err = sess.Save() require.NoError(t, err) // get session sess, err = store.Get(ctx) require.NoError(t, err) require.False(t, sess.Fresh()) // get value vuserResult, ok := sess.Get("vuser").(User) require.True(t, ok) require.Equal(t, vuser, vuserResult) vboolResult, ok := sess.Get("vbool").(bool) require.True(t, ok) require.Equal(t, vbool, vboolResult) vstringResult, ok := sess.Get("vstring").(string) require.True(t, ok) require.Equal(t, vstring, vstringResult) vintResult, ok := sess.Get("vint").(int) require.True(t, ok) require.Equal(t, vint, vintResult) vint8Result, ok := sess.Get("vint8").(int8) require.True(t, ok) require.Equal(t, vint8, vint8Result) vint16Result, ok := sess.Get("vint16").(int16) require.True(t, ok) require.Equal(t, vint16, vint16Result) vint32Result, ok := sess.Get("vint32").(int32) require.True(t, ok) require.Equal(t, vint32, vint32Result) vint64Result, ok := sess.Get("vint64").(int64) require.True(t, ok) require.Equal(t, vint64, vint64Result) vuintResult, ok := sess.Get("vuint").(uint) require.True(t, ok) require.Equal(t, vuint, vuintResult) vuint8Result, ok := sess.Get("vuint8").(uint8) require.True(t, ok) require.Equal(t, vuint8, vuint8Result) vuint16Result, ok := sess.Get("vuint16").(uint16) require.True(t, ok) require.Equal(t, vuint16, vuint16Result) vuint32Result, ok := sess.Get("vuint32").(uint32) require.True(t, ok) require.Equal(t, vuint32, vuint32Result) vuint64Result, ok := sess.Get("vuint64").(uint64) require.True(t, ok) require.Equal(t, vuint64, vuint64Result) vuintptrResult, ok := sess.Get("vuintptr").(uintptr) require.True(t, ok) require.Equal(t, vuintptr, vuintptrResult) vbyteResult, ok := sess.Get("vbyte").(byte) require.True(t, ok) require.Equal(t, vbyte, vbyteResult) vruneResult, ok := sess.Get("vrune").(rune) require.True(t, ok) require.Equal(t, vrune, vruneResult) vfloat32Result, ok := sess.Get("vfloat32").(float32) require.True(t, ok) require.InEpsilon(t, vfloat32, vfloat32Result, 0.001) vfloat64Result, ok := sess.Get("vfloat64").(float64) require.True(t, ok) require.InEpsilon(t, vfloat64, vfloat64Result, 0.001) vcomplex64Result, ok := sess.Get("vcomplex64").(complex64) require.True(t, ok) require.Equal(t, vcomplex64, vcomplex64Result) vcomplex128Result, ok := sess.Get("vcomplex128").(complex128) require.True(t, ok) require.Equal(t, vcomplex128, vcomplex128Result) } // go test -run Test_Session_Store_Reset func Test_Session_Store_Reset(t *testing.T) { t.Parallel() // session store store := New() // fiber instance app := fiber.New() // fiber context ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(ctx) // get session sess, err := store.Get(ctx) require.NoError(t, err) // make sure its new require.True(t, sess.Fresh()) // set value & save sess.Set("hello", "world") ctx.Request().Header.SetCookie(store.sessionName, sess.ID()) require.NoError(t, sess.Save()) // reset store require.NoError(t, store.Reset()) // make sure the session is recreated sess, err = store.Get(ctx) require.NoError(t, err) require.True(t, sess.Fresh()) require.Nil(t, sess.Get("hello")) } // go test -run Test_Session_Save func Test_Session_Save(t *testing.T) { t.Parallel() t.Run("save to cookie", func(t *testing.T) { t.Parallel() // session store store := New() // fiber instance app := fiber.New() // fiber context ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) // get session sess, err := store.Get(ctx) require.NoError(t, err) // set value sess.Set("name", "john") // save session err = sess.Save() require.NoError(t, err) }) t.Run("save to header", func(t *testing.T) { t.Parallel() // session store store := New(Config{ KeyLookup: "header:session_id", }) // fiber instance app := fiber.New() // fiber context ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(ctx) // get session sess, err := store.Get(ctx) require.NoError(t, err) // set value sess.Set("name", "john") // save session err = sess.Save() require.NoError(t, err) require.Equal(t, store.getSessionID(ctx), string(ctx.Response().Header.Peek(store.sessionName))) require.Equal(t, store.getSessionID(ctx), string(ctx.Request().Header.Peek(store.sessionName))) }) } func Test_Session_Save_Expiration(t *testing.T) { t.Parallel() t.Run("save to cookie", func(t *testing.T) { t.Parallel() const sessionDuration = 5 * time.Second // session store store := New() // fiber instance app := fiber.New() // fiber context ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(ctx) // get session sess, err := store.Get(ctx) require.NoError(t, err) // set value sess.Set("name", "john") // expire this session in 5 seconds sess.SetExpiry(sessionDuration) // save session err = sess.Save() require.NoError(t, err) // here you need to get the old session yet sess, err = store.Get(ctx) require.NoError(t, err) require.Equal(t, "john", sess.Get("name")) // just to make sure the session has been expired time.Sleep(sessionDuration + (10 * time.Millisecond)) // here you should get a new session sess, err = store.Get(ctx) require.NoError(t, err) require.Nil(t, sess.Get("name")) }) } // go test -run Test_Session_Destroy func Test_Session_Destroy(t *testing.T) { t.Parallel() t.Run("destroy from cookie", func(t *testing.T) { t.Parallel() // session store store := New() // fiber instance app := fiber.New() // fiber context ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(ctx) // get session sess, err := store.Get(ctx) require.NoError(t, err) sess.Set("name", "fenny") require.NoError(t, sess.Destroy()) name := sess.Get("name") require.Nil(t, name) }) t.Run("destroy from header", func(t *testing.T) { t.Parallel() // session store store := New(Config{ KeyLookup: "header:session_id", }) // fiber instance app := fiber.New() // fiber context ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(ctx) // get session sess, err := store.Get(ctx) require.NoError(t, err) // set value & save sess.Set("name", "fenny") require.NoError(t, sess.Save()) sess, err = store.Get(ctx) require.NoError(t, err) err = sess.Destroy() require.NoError(t, err) require.Equal(t, "", string(ctx.Response().Header.Peek(store.sessionName))) require.Equal(t, "", string(ctx.Request().Header.Peek(store.sessionName))) }) } // go test -run Test_Session_Custom_Config func Test_Session_Custom_Config(t *testing.T) { t.Parallel() store := New(Config{Expiration: time.Hour, KeyGenerator: func() string { return "very random" }}) require.Equal(t, time.Hour, store.Expiration) require.Equal(t, "very random", store.KeyGenerator()) store = New(Config{Expiration: 0}) require.Equal(t, ConfigDefault.Expiration, store.Expiration) } // go test -run Test_Session_Cookie func Test_Session_Cookie(t *testing.T) { t.Parallel() // session store store := New() // fiber instance app := fiber.New() // fiber context ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(ctx) // get session sess, err := store.Get(ctx) require.NoError(t, err) require.NoError(t, sess.Save()) // cookie should be set on Save ( even if empty data ) require.Len(t, ctx.Response().Header.PeekCookie(store.sessionName), 84) } // go test -run Test_Session_Cookie_In_Response func Test_Session_Cookie_In_Response(t *testing.T) { t.Parallel() store := New() app := fiber.New() // fiber context ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(ctx) // get session sess, err := store.Get(ctx) require.NoError(t, err) sess.Set("id", "1") require.True(t, sess.Fresh()) require.NoError(t, sess.Save()) sess, err = store.Get(ctx) require.NoError(t, err) sess.Set("name", "john") require.True(t, sess.Fresh()) require.Equal(t, "1", sess.Get("id")) require.Equal(t, "john", sess.Get("name")) } // go test -run Test_Session_Deletes_Single_Key // Regression: https://github.com/gofiber/fiber/issues/1365 func Test_Session_Deletes_Single_Key(t *testing.T) { t.Parallel() store := New() app := fiber.New() ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(ctx) sess, err := store.Get(ctx) require.NoError(t, err) ctx.Request().Header.SetCookie(store.sessionName, sess.ID()) sess.Set("id", "1") require.NoError(t, sess.Save()) sess, err = store.Get(ctx) require.NoError(t, err) sess.Delete("id") require.NoError(t, sess.Save()) sess, err = store.Get(ctx) require.NoError(t, err) require.False(t, sess.Fresh()) require.Nil(t, sess.Get("id")) } // go test -run Test_Session_Reset func Test_Session_Reset(t *testing.T) { t.Parallel() // fiber instance app := fiber.New() // session store store := New() t.Run("reset session data and id, and set fresh to be true", func(t *testing.T) { t.Parallel() // fiber context ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(ctx) // a random session uuid originalSessionUUIDString := "" // now the session is in the storage freshSession, err := store.Get(ctx) require.NoError(t, err) originalSessionUUIDString = freshSession.ID() // set a value freshSession.Set("name", "fenny") freshSession.Set("email", "fenny@example.com") err = freshSession.Save() require.NoError(t, err) // set cookie ctx.Request().Header.SetCookie(store.sessionName, originalSessionUUIDString) // as the session is in the storage, session.fresh should be false acquiredSession, err := store.Get(ctx) require.NoError(t, err) require.False(t, acquiredSession.Fresh()) err = acquiredSession.Reset() require.NoError(t, err) require.NotEqual(t, originalSessionUUIDString, acquiredSession.ID()) // acquiredSession.fresh should be true after resetting require.True(t, acquiredSession.Fresh()) // Check that the session data has been reset keys := acquiredSession.Keys() require.Equal(t, []string{}, keys) // Set a new value for 'name' and check that it's updated acquiredSession.Set("name", "john") require.Equal(t, "john", acquiredSession.Get("name")) require.Nil(t, acquiredSession.Get("email")) // Save after resetting err = acquiredSession.Save() require.NoError(t, err) // Check that the session id is not in the header or cookie anymore require.Equal(t, "", string(ctx.Response().Header.Peek(store.sessionName))) require.Equal(t, "", string(ctx.Request().Header.Peek(store.sessionName))) }) } // go test -run Test_Session_Regenerate // Regression: https://github.com/gofiber/fiber/issues/1395 func Test_Session_Regenerate(t *testing.T) { t.Parallel() // fiber instance app := fiber.New() t.Run("set fresh to be true when regenerating a session", func(t *testing.T) { t.Parallel() // session store store := New() // a random session uuid originalSessionUUIDString := "" // fiber context ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(ctx) // now the session is in the storage freshSession, err := store.Get(ctx) require.NoError(t, err) originalSessionUUIDString = freshSession.ID() err = freshSession.Save() require.NoError(t, err) // set cookie ctx.Request().Header.SetCookie(store.sessionName, originalSessionUUIDString) // as the session is in the storage, session.fresh should be false acquiredSession, err := store.Get(ctx) require.NoError(t, err) require.False(t, acquiredSession.Fresh()) err = acquiredSession.Regenerate() require.NoError(t, err) require.NotEqual(t, originalSessionUUIDString, acquiredSession.ID()) // acquiredSession.fresh should be true after regenerating require.True(t, acquiredSession.Fresh()) }) } // go test -v -run=^$ -bench=Benchmark_Session -benchmem -count=4 func Benchmark_Session(b *testing.B) { b.Run("default", func(b *testing.B) { app, store := fiber.New(), New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) c.Request().Header.SetCookie(store.sessionName, "12356789") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { sess, _ := store.Get(c) //nolint:errcheck // We're inside a benchmark sess.Set("john", "doe") _ = sess.Save() //nolint:errcheck // We're inside a benchmark } }) b.Run("storage", func(b *testing.B) { app := fiber.New() store := New(Config{ Storage: memory.New(), }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) c.Request().Header.SetCookie(store.sessionName, "12356789") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { sess, _ := store.Get(c) //nolint:errcheck // We're inside a benchmark sess.Set("john", "doe") _ = sess.Save() //nolint:errcheck // We're inside a benchmark } }) } // go test -v -run=^$ -bench=Benchmark_Session_Parallel -benchmem -count=4 func Benchmark_Session_Parallel(b *testing.B) { b.Run("default", func(b *testing.B) { app, store := fiber.New(), New() b.ReportAllocs() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.SetCookie(store.sessionName, "12356789") sess, _ := store.Get(c) //nolint:errcheck // We're inside a benchmark sess.Set("john", "doe") _ = sess.Save() //nolint:errcheck // We're inside a benchmark app.ReleaseCtx(c) } }) }) b.Run("storage", func(b *testing.B) { app := fiber.New() store := New(Config{ Storage: memory.New(), }) b.ReportAllocs() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.SetCookie(store.sessionName, "12356789") sess, _ := store.Get(c) //nolint:errcheck // We're inside a benchmark sess.Set("john", "doe") _ = sess.Save() //nolint:errcheck // We're inside a benchmark app.ReleaseCtx(c) } }) }) } // go test -v -run=^$ -bench=Benchmark_Session_Asserted -benchmem -count=4 func Benchmark_Session_Asserted(b *testing.B) { b.Run("default", func(b *testing.B) { app, store := fiber.New(), New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) c.Request().Header.SetCookie(store.sessionName, "12356789") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { sess, err := store.Get(c) require.NoError(b, err) sess.Set("john", "doe") err = sess.Save() require.NoError(b, err) } }) b.Run("storage", func(b *testing.B) { app := fiber.New() store := New(Config{ Storage: memory.New(), }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) c.Request().Header.SetCookie(store.sessionName, "12356789") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { sess, err := store.Get(c) require.NoError(b, err) sess.Set("john", "doe") err = sess.Save() require.NoError(b, err) } }) } // go test -v -run=^$ -bench=Benchmark_Session_Asserted_Parallel -benchmem -count=4 func Benchmark_Session_Asserted_Parallel(b *testing.B) { b.Run("default", func(b *testing.B) { app, store := fiber.New(), New() b.ReportAllocs() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.SetCookie(store.sessionName, "12356789") sess, err := store.Get(c) require.NoError(b, err) sess.Set("john", "doe") require.NoError(b, sess.Save()) app.ReleaseCtx(c) } }) }) b.Run("storage", func(b *testing.B) { app := fiber.New() store := New(Config{ Storage: memory.New(), }) b.ReportAllocs() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.SetCookie(store.sessionName, "12356789") sess, err := store.Get(c) require.NoError(b, err) sess.Set("john", "doe") require.NoError(b, sess.Save()) app.ReleaseCtx(c) } }) }) }