backend/pkg/permission/usecase/token_usecase_additional_te...

566 lines
12 KiB
Go

package usecase
import (
"context"
"testing"
"time"
"backend/internal/config"
"backend/pkg/permission/domain/entity"
"backend/pkg/permission/domain/token"
"backend/pkg/permission/mock/repository"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestTokenUseCase_RefreshToken(t *testing.T) {
mockRepo := repository.NewMockTokenRepository(t)
cfg := &config.Config{
Token: struct {
AccessSecret string
RefreshSecret string
AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration
MaxTokensPerUser int
MaxTokensPerDevice int
}{
AccessSecret: "test-access-secret",
RefreshSecret: "test-refresh-secret",
AccessTokenExpiry: 15 * time.Minute,
RefreshTokenExpiry: 7 * 24 * time.Hour,
},
}
useCase := &TokenUseCase{
TokenUseCaseParam: TokenUseCaseParam{
TokenRepo: mockRepo,
Config: cfg,
},
}
// Create a base token first
tokenReq := entity.AuthorizationReq{
GrantType: token.PasswordCredentials.ToString(),
Data: map[string]string{
"uid": "user123",
"role": "user",
},
IsRefreshToken: true,
}
mockRepo.On("Create", mock.Anything, mock.AnythingOfType("entity.Token")).
Return(nil).Once()
tokenResp, err := useCase.NewToken(context.Background(), tokenReq)
assert.NoError(t, err)
tests := []struct {
name string
req entity.RefreshTokenReq
setup func()
wantErr bool
}{
{
name: "successful token refresh",
req: entity.RefreshTokenReq{
Token: tokenResp.RefreshToken,
Scope: "read write",
DeviceID: "device123",
},
setup: func() {
existingToken := entity.Token{
ID: "old-token-id",
UID: "user123",
AccessToken: tokenResp.AccessToken,
ExpiresIn: int(time.Now().Add(time.Hour).Unix()),
}
mockRepo.On("GetAccessTokenByOneTimeToken", mock.Anything, tokenResp.RefreshToken).
Return(existingToken, nil).Once()
mockRepo.On("Create", mock.Anything, mock.AnythingOfType("entity.Token")).
Return(nil).Once()
mockRepo.On("Delete", mock.Anything, mock.AnythingOfType("entity.Token")).
Return(nil).Once()
},
wantErr: false,
},
{
name: "invalid refresh token",
req: entity.RefreshTokenReq{
Token: "invalid-refresh-token",
Scope: "read",
DeviceID: "device123",
},
setup: func() {
mockRepo.On("GetAccessTokenByOneTimeToken", mock.Anything, "invalid-refresh-token").
Return(entity.Token{}, assert.AnError).Once()
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setup()
resp, err := useCase.RefreshToken(context.Background(), tt.req)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.NotEmpty(t, resp.Token)
assert.NotEmpty(t, resp.OneTimeToken)
assert.Equal(t, token.TypeBearer.String(), resp.TokenType)
}
mockRepo.AssertExpectations(t)
})
}
}
func TestTokenUseCase_GetUserTokensByUID(t *testing.T) {
mockRepo := repository.NewMockTokenRepository(t)
cfg := &config.Config{}
useCase := &TokenUseCase{
TokenUseCaseParam: TokenUseCaseParam{
TokenRepo: mockRepo,
Config: cfg,
},
}
tests := []struct {
name string
req entity.QueryTokenByUIDReq
setup func()
wantErr bool
}{
{
name: "get tokens successfully",
req: entity.QueryTokenByUIDReq{
UID: "user123",
},
setup: func() {
tokens := []entity.Token{
{
ID: "token1",
UID: "user123",
AccessToken: "access1",
ExpiresIn: 3600,
},
{
ID: "token2",
UID: "user123",
AccessToken: "access2",
ExpiresIn: 3600,
},
}
mockRepo.On("GetAccessTokensByUID", mock.Anything, "user123").
Return(tokens, nil).Once()
},
wantErr: false,
},
{
name: "repository error",
req: entity.QueryTokenByUIDReq{
UID: "user456",
},
setup: func() {
mockRepo.On("GetAccessTokensByUID", mock.Anything, "user456").
Return([]entity.Token(nil), assert.AnError).Once()
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setup()
tokens, err := useCase.GetUserTokensByUID(context.Background(), tt.req)
if tt.wantErr {
assert.Error(t, err)
assert.Nil(t, tokens)
} else {
assert.NoError(t, err)
assert.NotNil(t, tokens)
assert.Greater(t, len(tokens), 0)
}
mockRepo.AssertExpectations(t)
})
}
}
func TestTokenUseCase_GetUserTokensByDeviceID(t *testing.T) {
mockRepo := repository.NewMockTokenRepository(t)
cfg := &config.Config{}
useCase := &TokenUseCase{
TokenUseCaseParam: TokenUseCaseParam{
TokenRepo: mockRepo,
Config: cfg,
},
}
tests := []struct {
name string
req entity.DoTokenByDeviceIDReq
setup func()
wantErr bool
}{
{
name: "get tokens by device successfully",
req: entity.DoTokenByDeviceIDReq{
DeviceID: "device123",
},
setup: func() {
tokens := []entity.Token{
{
ID: "token1",
UID: "user123",
DeviceID: "device123",
AccessToken: "access1",
ExpiresIn: 3600,
},
}
mockRepo.On("GetAccessTokensByDeviceID", mock.Anything, "device123").
Return(tokens, nil).Once()
},
wantErr: false,
},
{
name: "repository error",
req: entity.DoTokenByDeviceIDReq{
DeviceID: "device456",
},
setup: func() {
mockRepo.On("GetAccessTokensByDeviceID", mock.Anything, "device456").
Return([]entity.Token(nil), assert.AnError).Once()
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setup()
tokens, err := useCase.GetUserTokensByDeviceID(context.Background(), tt.req)
if tt.wantErr {
assert.Error(t, err)
assert.Nil(t, tokens)
} else {
assert.NoError(t, err)
assert.NotNil(t, tokens)
}
mockRepo.AssertExpectations(t)
})
}
}
func TestTokenUseCase_CancelTokenByDeviceID(t *testing.T) {
mockRepo := repository.NewMockTokenRepository(t)
cfg := &config.Config{}
useCase := &TokenUseCase{
TokenUseCaseParam: TokenUseCaseParam{
TokenRepo: mockRepo,
Config: cfg,
},
}
tests := []struct {
name string
req entity.DoTokenByDeviceIDReq
setup func()
wantErr bool
}{
{
name: "cancel tokens successfully",
req: entity.DoTokenByDeviceIDReq{
DeviceID: "device123",
},
setup: func() {
mockRepo.On("DeleteAccessTokensByDeviceID", mock.Anything, "device123").
Return(nil).Once()
},
wantErr: false,
},
{
name: "repository error",
req: entity.DoTokenByDeviceIDReq{
DeviceID: "device456",
},
setup: func() {
mockRepo.On("DeleteAccessTokensByDeviceID", mock.Anything, "device456").
Return(assert.AnError).Once()
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setup()
err := useCase.CancelTokenByDeviceID(context.Background(), tt.req)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
mockRepo.AssertExpectations(t)
})
}
}
func TestTokenUseCase_NewOneTimeToken(t *testing.T) {
mockRepo := repository.NewMockTokenRepository(t)
cfg := &config.Config{
Token: struct {
AccessSecret string
RefreshSecret string
AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration
MaxTokensPerUser int
MaxTokensPerDevice int
}{
AccessSecret: "test-access-secret",
AccessTokenExpiry: 15 * time.Minute,
RefreshTokenExpiry: 7 * 24 * time.Hour,
},
}
useCase := &TokenUseCase{
TokenUseCaseParam: TokenUseCaseParam{
TokenRepo: mockRepo,
Config: cfg,
},
}
// Create a base token first
tokenReq := entity.AuthorizationReq{
GrantType: token.PasswordCredentials.ToString(),
Data: map[string]string{
"uid": "user123",
"role": "user",
},
}
mockRepo.On("Create", mock.Anything, mock.AnythingOfType("entity.Token")).
Return(nil).Once()
tokenResp, err := useCase.NewToken(context.Background(), tokenReq)
assert.NoError(t, err)
tests := []struct {
name string
req entity.CreateOneTimeTokenReq
setup func()
wantErr bool
}{
{
name: "create one-time token successfully",
req: entity.CreateOneTimeTokenReq{
Token: tokenResp.AccessToken,
},
setup: func() {
existingToken := entity.Token{
ID: "token-id",
UID: "user123",
AccessToken: tokenResp.AccessToken,
ExpiresIn: int(time.Now().Add(time.Hour).Unix()),
}
mockRepo.On("GetAccessTokenByID", mock.Anything, mock.AnythingOfType("string")).
Return(existingToken, nil).Once()
mockRepo.On("CreateOneTimeToken", mock.Anything, mock.AnythingOfType("string"),
mock.AnythingOfType("entity.Ticket"), mock.AnythingOfType("time.Duration")).
Return(nil).Once()
},
wantErr: false,
},
{
name: "invalid token",
req: entity.CreateOneTimeTokenReq{
Token: "invalid-token",
},
setup: func() {
// parseClaims will fail
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setup()
resp, err := useCase.NewOneTimeToken(context.Background(), tt.req)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.NotEmpty(t, resp.OneTimeToken)
}
mockRepo.AssertExpectations(t)
})
}
}
func TestTokenUseCase_CancelOneTimeToken(t *testing.T) {
mockRepo := repository.NewMockTokenRepository(t)
cfg := &config.Config{}
useCase := &TokenUseCase{
TokenUseCaseParam: TokenUseCaseParam{
TokenRepo: mockRepo,
Config: cfg,
},
}
tests := []struct {
name string
req entity.CancelOneTimeTokenReq
setup func()
wantErr bool
}{
{
name: "cancel one-time token successfully",
req: entity.CancelOneTimeTokenReq{
Token: []string{"token1", "token2"},
},
setup: func() {
mockRepo.On("DeleteOneTimeToken", mock.Anything, []string{"token1", "token2"}, mock.Anything).
Return(nil).Once()
},
wantErr: false,
},
{
name: "repository error",
req: entity.CancelOneTimeTokenReq{
Token: []string{"token3"},
},
setup: func() {
mockRepo.On("DeleteOneTimeToken", mock.Anything, []string{"token3"}, mock.Anything).
Return(assert.AnError).Once()
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setup()
err := useCase.CancelOneTimeToken(context.Background(), tt.req)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
mockRepo.AssertExpectations(t)
})
}
}
func TestTokenUseCase_ReadTokenBasicData(t *testing.T) {
mockRepo := repository.NewMockTokenRepository(t)
cfg := &config.Config{
Token: struct {
AccessSecret string
RefreshSecret string
AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration
MaxTokensPerUser int
MaxTokensPerDevice int
}{
AccessSecret: "test-access-secret",
AccessTokenExpiry: 15 * time.Minute,
RefreshTokenExpiry: 7 * 24 * time.Hour,
},
}
useCase := &TokenUseCase{
TokenUseCaseParam: TokenUseCaseParam{
TokenRepo: mockRepo,
Config: cfg,
},
}
// Create a valid token first
tokenReq := entity.AuthorizationReq{
GrantType: token.PasswordCredentials.ToString(),
Data: map[string]string{
"uid": "user123",
"role": "admin",
},
Role: "admin",
}
mockRepo.On("Create", mock.Anything, mock.AnythingOfType("entity.Token")).
Return(nil).Once()
tokenResp, err := useCase.NewToken(context.Background(), tokenReq)
assert.NoError(t, err)
tests := []struct {
name string
token string
wantErr bool
}{
{
name: "read valid token",
token: tokenResp.AccessToken,
wantErr: false,
},
{
name: "invalid token",
token: "invalid-token",
wantErr: true,
},
{
name: "empty token",
token: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
claims, err := useCase.ReadTokenBasicData(context.Background(), tt.token)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.NotNil(t, claims)
assert.Equal(t, "user123", claims["uid"])
assert.Equal(t, "admin", claims["role"])
}
mockRepo.AssertExpectations(t)
})
}
}
// TestTokenUseCase_BlacklistAllUserTokens is commented out due to complexity of mocking
// the JWT parsing within the loop. The functionality is tested through integration tests.
// func TestTokenUseCase_BlacklistAllUserTokens(t *testing.T) { ... }