package controllers_test import ( "context" "encoding/json" "testing" "time" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" "github.com/google/uuid" "github.com/stretchr/testify/assert" ) func newTestTokensController(ctx context.Context) *controllers.TokenController { return &controllers.TokenController{ DB: newTestDBConnection(ctx), Redis: newTestRedisConnection(), } } func TestIntegrationCreateToken_Success(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) accountData := &controllers.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } id, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) tokenData := &controllers.TokenData{ Name: "Test Token", UserID: id, ExpiresAt: time.Now().Add(time.Second * 5), Scopes: map[string][]string{ "Test": {"test", "test2"}, }, } ctrl := newTestTokensController(t.Context()) tokenVal, tokenID, err := ctrl.Create(t.Context(), tokenData) assert.NoError(t, err) assert.NotEmpty(t, tokenID) assert.NotEmpty(t, tokenVal) } func TestIntegrationCreateToken_UserNotExist(t *testing.T) { tokenData := &controllers.TokenData{ Name: "Test Token", UserID: uuid.NewString(), ExpiresAt: time.Now().Add(time.Second * 5), Scopes: map[string][]string{ "Test": {"test", "test2"}, }, } ctrl := newTestTokensController(t.Context()) tokenVal, tokenID, err := ctrl.Create(t.Context(), tokenData) assert.Error(t, err) assert.ErrorIs(t, err, controllers.ErrUserNotFound) assert.Empty(t, tokenID) assert.Empty(t, tokenVal) } func TestIntegrationGetToken_Success(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) accountData := &controllers.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } now := time.Now() userID, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) tokenData := &controllers.TokenData{ Name: "Test Token", UserID: userID, ExpiresAt: time.Now().Add(time.Second * 5), Scopes: map[string][]string{ "Test": {"test", "test2"}, }, } ctrl := newTestTokensController(t.Context()) _, tokenID, err := ctrl.Create(t.Context(), tokenData) assert.NoError(t, err) token, err := ctrl.Get(t.Context(), tokenID, userID) assert.NoError(t, err) assert.Equal(t, now.Truncate(time.Second), token.GeneratedAt.Truncate(time.Second)) assert.Equal(t, now.Truncate(time.Second), token.CreatedAt.Truncate(time.Second)) assert.Equal(t, tokenData.Scopes, token.Scopes) assert.Equal(t, tokenData.Name, token.Name) assert.Equal(t, tokenData.ExpiresAt.Truncate(time.Second), token.ExpiresAt.Truncate(time.Second)) } func TestIntegrationGetToken_NotExists(t *testing.T) { ctrl := newTestTokensController(t.Context()) token, err := ctrl.Get(t.Context(), uuid.NewString(), uuid.NewString()) assert.Error(t, err) assert.ErrorIs(t, err, controllers.ErrTokenNotFound) assert.Empty(t, token) } func TestIntegrationGetToken_WrongRequest(t *testing.T) { ctrl := newTestTokensController(t.Context()) token, err := ctrl.Get(t.Context(), "test", "test") assert.Error(t, err) assert.ErrorIs(t, err, controllers.ErrServerError) assert.Empty(t, token) } func TestIntegrationVerifyTokenOwner_Success(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) accountData := &controllers.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } userID, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) tokenData := &controllers.TokenData{ Name: "Test Token", UserID: userID, ExpiresAt: time.Now().Add(time.Second * 5), Scopes: map[string][]string{ "Test": {"test", "test2"}, }, } ctrl := newTestTokensController(t.Context()) _, tokenID, err := ctrl.Create(t.Context(), tokenData) assert.NoError(t, err) assert.NoError(t, ctrl.VerifyTokenOwner(t.Context(), userID, tokenID)) } func TestIntegrationVerifyTokenOwner_WrongOwner(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) accountData := &controllers.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } secondAccountData := &controllers.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } userID, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) secondUserID, err := ctrlAccount.Create(t.Context(), secondAccountData) assert.NoError(t, err) tokenData := &controllers.TokenData{ Name: "Test Token", UserID: userID, ExpiresAt: time.Now().Add(time.Second * 5), Scopes: map[string][]string{ "Test": {"test", "test2"}, }, } ctrl := newTestTokensController(t.Context()) _, tokenID, err := ctrl.Create(t.Context(), tokenData) assert.NoError(t, err) assert.ErrorIs(t, ctrl.VerifyTokenOwner(t.Context(), secondUserID, tokenID), controllers.ErrUserTokenMismatch) } func TestIntegrationForceExpiration_Success(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) accountData := &controllers.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } userID, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) tokenData := &controllers.TokenData{ Name: "Test Token", UserID: userID, ExpiresAt: time.Now().Add(time.Second * 5), Scopes: map[string][]string{ "Test": {"test", "test2"}, }, } ctrl := newTestTokensController(t.Context()) _, tokenID, err := ctrl.Create(t.Context(), tokenData) assert.NoError(t, err) now := time.Now() assert.NoError(t, ctrl.ForceExpiration(t.Context(), tokenID)) token, err := ctrl.Get(t.Context(), tokenID, userID) assert.NoError(t, err) assert.Equal(t, now.Truncate(time.Second), token.RevokedAt.Truncate(time.Second)) } func TestIntegrationRegenerateToken_Success(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) accountData := &controllers.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } now := time.Now() userID, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) tokenData := &controllers.TokenData{ Name: "Test Token", UserID: userID, ExpiresAt: time.Now().Add(time.Second * 5), Scopes: map[string][]string{ "Test": {"test", "test2"}, }, } ctrl := newTestTokensController(t.Context()) _, tokenID, err := ctrl.Create(t.Context(), tokenData) assert.NoError(t, err) token, err := ctrl.Get(t.Context(), tokenID, userID) assert.NoError(t, err) assert.Equal(t, now.Truncate(time.Second), token.GeneratedAt.Truncate(time.Second)) assert.Equal(t, now.Truncate(time.Second), token.CreatedAt.Truncate(time.Second)) assert.Equal(t, tokenData.Scopes, token.Scopes) assert.Equal(t, tokenData.Name, token.Name) assert.Equal(t, tokenData.ExpiresAt.Truncate(time.Second), token.ExpiresAt.Truncate(time.Second)) time.Sleep(5 * time.Second) newNow := time.Now() newToken, err := ctrl.Regenerate(t.Context(), tokenID) assert.NotEmpty(t, newToken) assert.NoError(t, err) newExpiresAt := tokenData.ExpiresAt.Add(5 * time.Second) updatedToken, err := ctrl.Get(t.Context(), tokenID, userID) assert.NoError(t, err) assert.Equal(t, newNow.Truncate(time.Second), updatedToken.GeneratedAt.Truncate(time.Second)) assert.Equal(t, now.Truncate(time.Second), updatedToken.CreatedAt.Truncate(time.Second)) assert.Equal(t, tokenData.Scopes, updatedToken.Scopes) assert.Equal(t, tokenData.Name, updatedToken.Name) assert.Equal(t, newExpiresAt.Truncate(time.Second), updatedToken.ExpiresAt.Truncate(time.Second)) } func TestIntegrationListTokens_Success(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) accountData := &controllers.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } userID, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) tokenDataOne := &controllers.TokenData{ Name: "Test Token", UserID: userID, ExpiresAt: time.Now().Add(time.Second * 5), Scopes: map[string][]string{ "Test": {"test", "test2"}, }, } tokenDataTwo := &controllers.TokenData{ Name: "Test Token again", UserID: userID, ExpiresAt: time.Now().Add(time.Second * 5), Scopes: map[string][]string{ "Test": {"test", "test2"}, }, } ctrl := newTestTokensController(t.Context()) _, _, err = ctrl.Create(t.Context(), tokenDataOne) assert.NoError(t, err) _, _, err = ctrl.Create(t.Context(), tokenDataTwo) assert.NoError(t, err) tokens, err := ctrl.List(t.Context(), userID) assert.NoError(t, err) assert.Len(t, tokens, 2) } func TestIntegrationAuthenticateWithToken_Success(t *testing.T) { ctrlAccount := newTestAccountController(t.Context()) accountData := &controllers.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } id, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) tokenData := &controllers.TokenData{ Name: "Test Token", UserID: id, ExpiresAt: time.Now().Add(time.Second * 5), Scopes: map[string][]string{ "Test": {"test", "test2"}, }, } ctrl := newTestTokensController(t.Context()) tokenVal, _, err := ctrl.Create(t.Context(), tokenData) assert.NoError(t, err) auth, err := ctrl.AuthenticateWithToken(t.Context(), tokenVal) assert.NoError(t, err) assert.NotEmpty(t, auth.Scope) assert.NotEmpty(t, auth.UserID) assert.Equal(t, id, auth.UserID) scope := map[string][]string{} assert.NoError(t, json.Unmarshal([]byte(auth.Scope), &scope)) assert.NoError(t, err) assert.Equal(t, tokenData.Scopes, scope) } func TestIntegrationAuthenticateWithToken_UnknownToken(t *testing.T) { ctrl := newTestTokensController(t.Context()) auth, err := ctrl.AuthenticateWithToken(t.Context(), "dummy") assert.Error(t, err) assert.Nil(t, auth) assert.ErrorIs(t, err, controllers.ErrTokenNotFound) } func TestIntegrationAuthenticateWithToken_Expired(t *testing.T) { ctrlAccount := newTestAccountController(t.Context()) accountData := &controllers.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } id, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) tokenData := &controllers.TokenData{ Name: "Test Token", UserID: id, ExpiresAt: time.Now().Add(time.Second * 5), Scopes: map[string][]string{ "Test": {"test", "test2"}, }, } ctrl := newTestTokensController(t.Context()) tokenVal, _, err := ctrl.Create(t.Context(), tokenData) assert.NoError(t, err) auth, err := ctrl.AuthenticateWithToken(t.Context(), tokenVal) assert.NoError(t, err) assert.NotEmpty(t, auth.Scope) assert.NotEmpty(t, auth.UserID) assert.Equal(t, id, auth.UserID) time.Sleep(time.Second * 6) auth, err = ctrl.AuthenticateWithToken(t.Context(), tokenVal) assert.Error(t, err) assert.Nil(t, auth) assert.ErrorIs(t, err, controllers.ErrBadToken) } func TestIntegrationAuthenticateWithToken_Revoked(t *testing.T) { ctrlAccount := newTestAccountController(t.Context()) accountData := &controllers.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } id, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) tokenData := &controllers.TokenData{ Name: "Test Token", UserID: id, ExpiresAt: time.Now().Add(time.Second * 5), Scopes: map[string][]string{ "Test": {"test", "test2"}, }, } ctrl := newTestTokensController(t.Context()) tokenVal, tokenID, err := ctrl.Create(t.Context(), tokenData) assert.NoError(t, err) auth, err := ctrl.AuthenticateWithToken(t.Context(), tokenVal) assert.NoError(t, err) assert.NotEmpty(t, auth.Scope) assert.NotEmpty(t, auth.UserID) assert.Equal(t, id, auth.UserID) assert.NoError(t, ctrl.ForceExpiration(t.Context(), tokenID)) auth, err = ctrl.AuthenticateWithToken(t.Context(), tokenVal) assert.Error(t, err) assert.Nil(t, auth) assert.ErrorIs(t, err, controllers.ErrBadToken) }