diff --git a/internal/logic/new_token_logic_test.go b/internal/logic/new_token_logic_test.go new file mode 100644 index 0000000..35ab56a --- /dev/null +++ b/internal/logic/new_token_logic_test.go @@ -0,0 +1,209 @@ +package logic + +import ( + "ark-permission/gen_result/pb/permission" + "ark-permission/internal/domain" + "ark-permission/internal/entity" + libMock "ark-permission/internal/mock/lib" + repoMock "ark-permission/internal/mock/repository" + "ark-permission/internal/svc" + "errors" + "github.com/stretchr/testify/assert" + + "context" + "github.com/golang-jwt/jwt/v4" + "go.uber.org/mock/gomock" + "testing" + "time" +) + +func TestNewTokenLogic_NewToken(t *testing.T) { + // mock + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + tokenMockRepo := repoMock.NewMockTokenRepository(ctrl) + mockValidate := libMock.NewMockValidate(ctrl) + + sc := svc.ServiceContext{ + TokenRedisRepo: tokenMockRepo, + Validate: mockValidate, + } + + l := NewNewTokenLogic(context.Background(), &sc) + + tests := []struct { + name string + input *permission.AuthorizationReq + setupMocks func() + expectError bool + expected *permission.TokenResp + }{ + { + name: "Valid token request", + input: &permission.AuthorizationReq{ + GrantType: "authorization_code", + DeviceId: "device123", + Scope: "read", + Expires: 3600, + IsRefreshToken: false, + Data: map[string]string{ + "uid": "user123", + }, + }, + setupMocks: func() { + mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil) + tokenMockRepo.EXPECT().Create(gomock.Any(), gomock.Any()).Return(nil).Do(func(ctx context.Context, token entity.Token) { + token.AccessToken = "access_token" + }) + generateAccessTokenFunc = func(token entity.Token, data any, sign string) (string, error) { + return "access_token", nil + } + generateRefreshTokenFunc = func(accessToken string) string { + return "refresh_token" + } + }, + expectError: false, + expected: &permission.TokenResp{ + AccessToken: "access_token", + TokenType: domain.TokenTypeBearer, + ExpiresIn: 3600, + RefreshToken: "", + }, + }, + { + name: "Validation error", + input: &permission.AuthorizationReq{ + GrantType: "invalid_grant", + DeviceId: "device123", + Scope: "read", + Expires: 3600, + IsRefreshToken: false, + Data: map[string]string{ + "uid": "user123", + }, + }, + setupMocks: func() { + mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("invalid grant type")) + }, + expectError: true, + expected: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setupMocks() + + resp, err := l.NewToken(tt.input) + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expected, resp) + } + }) + } +} + +// 測試 generateAccessToken 函數 +func TestGenerateAccessToken(t *testing.T) { + // 定義測試用例 + tests := []struct { + name string + token entity.Token + data any + sign string + shouldFail bool + shouldVerify bool + }{ + { + name: "Valid token with admin role", + token: entity.Token{ + ID: "123", + ExpiresIn: int(time.Now().Add(time.Hour * 24).Unix()), + }, + data: map[string]string{"role": "admin"}, + sign: "secret", + shouldFail: false, + shouldVerify: true, + }, + { + name: "Expired token", + token: entity.Token{ + ID: "456", + ExpiresIn: int(time.Now().Add(-time.Hour * 24).Unix()), // 過期時間 + }, + data: map[string]string{"role": "user"}, + sign: "secret", + shouldFail: false, // 這個測試不會失敗,因為過期檢查通常在驗證時進行 + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tokenString, err := generateAccessToken(tt.token, tt.data, tt.sign) + if (err != nil) != tt.shouldFail { + t.Errorf("generateAccessToken() error = %v, shouldFail %v", err, tt.shouldFail) + return + } + + if tt.shouldVerify { + // 驗證生成的 token + parsedToken, err := jwt.ParseWithClaims(tokenString, &entity.Claims{}, func(token *jwt.Token) (interface{}, error) { + return []byte(tt.sign), nil + }) + if err != nil { + t.Errorf("Error parsing token: %v", err) + return + } + + if claims, ok := parsedToken.Claims.(*entity.Claims); ok && parsedToken.Valid { + if claims.ID != tt.token.ID { + t.Errorf("Expected ID %v, got %v", tt.token.ID, claims.ID) + } + if claims.Issuer != "permission" { + t.Errorf("Expected Issuer 'permission', got %v", claims.Issuer) + } + for k, v := range tt.data.(map[string]string) { + if claims.Data.(map[string]any)[k] != v { + t.Errorf("Expected data %v, got %v", v, claims.Data.(map[string]string)[k]) + } + } + } else { + t.Errorf("Invalid token claims") + } + } + }) + } +} + +// 測試 generateRefreshToken 函數 +func TestGenerateRefreshToken(t *testing.T) { + // 定義測試用例 + tests := []struct { + accessToken string + expected string + }{ + { + accessToken: "test_access_token", + expected: "4993552f2cc6c4e57fa5738f9b161a1a4051c8370cddb32514c8f6f4c797801f", + }, + { + accessToken: "another_test_access_token", + expected: "8361833e9a11f829f2be9a00f1939b5a72408ff829451169f3b223c41768cfa2", + }, + { + accessToken: "", + expected: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, + } + + for _, tt := range tests { + t.Run(tt.accessToken, func(t *testing.T) { + got := generateRefreshToken(tt.accessToken) + if got != tt.expected { + t.Errorf("generateRefreshToken(%s) = %s; want %s", tt.accessToken, got, tt.expected) + } + }) + } +} diff --git a/internal/mock/lib/validate.go b/internal/mock/lib/validate.go new file mode 100644 index 0000000..123fe29 --- /dev/null +++ b/internal/mock/lib/validate.go @@ -0,0 +1,72 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./validate.go +// +// Generated by this command: +// +// mockgen -source=./validate.go -destination=../../mock/lib/validate.go -package=lib +// + +// Package lib is a generated GoMock package. +package lib + +import ( + required "ark-permission/internal/lib/required" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockValidate is a mock of Validate interface. +type MockValidate struct { + ctrl *gomock.Controller + recorder *MockValidateMockRecorder +} + +// MockValidateMockRecorder is the mock recorder for MockValidate. +type MockValidateMockRecorder struct { + mock *MockValidate +} + +// NewMockValidate creates a new mock instance. +func NewMockValidate(ctrl *gomock.Controller) *MockValidate { + mock := &MockValidate{ctrl: ctrl} + mock.recorder = &MockValidateMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockValidate) EXPECT() *MockValidateMockRecorder { + return m.recorder +} + +// BindToValidator mocks base method. +func (m *MockValidate) BindToValidator(opts ...required.Option) error { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "BindToValidator", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// BindToValidator indicates an expected call of BindToValidator. +func (mr *MockValidateMockRecorder) BindToValidator(opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BindToValidator", reflect.TypeOf((*MockValidate)(nil).BindToValidator), opts...) +} + +// ValidateAll mocks base method. +func (m *MockValidate) ValidateAll(obj any) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateAll", obj) + ret0, _ := ret[0].(error) + return ret0 +} + +// ValidateAll indicates an expected call of ValidateAll. +func (mr *MockValidateMockRecorder) ValidateAll(obj any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateAll", reflect.TypeOf((*MockValidate)(nil).ValidateAll), obj) +} diff --git a/internal/mock/repository/token.go b/internal/mock/repository/token.go new file mode 100644 index 0000000..6ef99eb --- /dev/null +++ b/internal/mock/repository/token.go @@ -0,0 +1,55 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./token.go +// +// Generated by this command: +// +// mockgen -source=./token.go -destination=../../mock/repository/token.go -package=repository +// + +// Package repository is a generated GoMock package. +package repository + +import ( + entity "ark-permission/internal/entity" + context "context" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockTokenRepository is a mock of TokenRepository interface. +type MockTokenRepository struct { + ctrl *gomock.Controller + recorder *MockTokenRepositoryMockRecorder +} + +// MockTokenRepositoryMockRecorder is the mock recorder for MockTokenRepository. +type MockTokenRepositoryMockRecorder struct { + mock *MockTokenRepository +} + +// NewMockTokenRepository creates a new mock instance. +func NewMockTokenRepository(ctrl *gomock.Controller) *MockTokenRepository { + mock := &MockTokenRepository{ctrl: ctrl} + mock.recorder = &MockTokenRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTokenRepository) EXPECT() *MockTokenRepositoryMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockTokenRepository) Create(ctx context.Context, token entity.Token) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", ctx, token) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockTokenRepositoryMockRecorder) Create(ctx, token any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockTokenRepository)(nil).Create), ctx, token) +}