package auth import ( "encoding/base64" "encoding/json" "strings" "testing" "time" "perms-system-server/internal/middleware" "github.com/golang-jwt/jwt/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const testSecret = "test-jwt-secret-key" // TC-0292: secret="s", expire=3600, userId=1, username="u", productCode="p", memberType="M" func TestGenerateAccessToken(t *testing.T) { tests := []struct { name string secret string expire int64 userId int64 username string productCode string memberType string tokenVersion int64 }{ { name: "normal generation", secret: testSecret, expire: 3600, userId: 1, username: "admin", productCode: "p1", memberType: "ADMIN", }, { name: "empty productCode", secret: testSecret, expire: 3600, userId: 3, username: "user2", productCode: "", memberType: "", }, { name: "super admin with tokenVersion", secret: testSecret, expire: 7200, userId: 100, username: "super", productCode: "p1", memberType: "SUPER_ADMIN", tokenVersion: 5, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tokenStr, err := GenerateAccessToken(tt.secret, tt.expire, tt.userId, tt.username, tt.productCode, tt.memberType, tt.tokenVersion) require.NoError(t, err) assert.NotEmpty(t, tokenStr) token, err := jwt.ParseWithClaims(tokenStr, &middleware.Claims{}, func(token *jwt.Token) (interface{}, error) { return []byte(tt.secret), nil }) require.NoError(t, err) assert.True(t, token.Valid) claims, ok := token.Claims.(*middleware.Claims) require.True(t, ok) assert.Equal(t, tt.userId, claims.UserId) assert.Equal(t, tt.username, claims.Username) assert.Equal(t, tt.productCode, claims.ProductCode) assert.Equal(t, tt.memberType, claims.MemberType) assert.Equal(t, tt.tokenVersion, claims.TokenVersion) // 审计修复项 M-6:`perms` 字段已从 Claims 结构体中移除。 // 解析原始 JWT payload,确保 token JSON 中不存在 "perms" key。 segments := strings.Split(tokenStr, ".") require.Len(t, segments, 3, "jwt must have 3 segments") payloadBytes, err := base64.RawURLEncoding.DecodeString(segments[1]) require.NoError(t, err) var raw map[string]interface{} require.NoError(t, json.Unmarshal(payloadBytes, &raw)) _, hasPerms := raw["perms"] assert.False(t, hasPerms, "access token payload must NOT contain perms field (audit M-6)") }) } } // TC-0296: expireSeconds=1, sleep 2s func TestGenerateAccessToken_Expiry(t *testing.T) { tokenStr, err := GenerateAccessToken(testSecret, 1, 1, "u", "", "", 0) require.NoError(t, err) time.Sleep(2 * time.Second) _, err = jwt.ParseWithClaims(tokenStr, &middleware.Claims{}, func(token *jwt.Token) (interface{}, error) { return []byte(testSecret), nil }) assert.Error(t, err) assert.Contains(t, err.Error(), "token is expired") } // TC-0297: secret="s", expire=86400, userId=1, productCode="p" func TestGenerateRefreshToken(t *testing.T) { tests := []struct { name string secret string expire int64 userId int64 productCode string }{ {"normal", testSecret, 86400, 1, "p1"}, {"empty productCode", testSecret, 86400, 2, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tokenStr, err := GenerateRefreshToken(tt.secret, tt.expire, tt.userId, tt.productCode, 0) require.NoError(t, err) assert.NotEmpty(t, tokenStr) claims, err := ParseRefreshToken(tokenStr, tt.secret) require.NoError(t, err) assert.Equal(t, tt.userId, claims.UserId) assert.Equal(t, tt.productCode, claims.ProductCode) }) } } // TC-0300: 有效token+正确secret func TestParseRefreshToken(t *testing.T) { validToken, err := GenerateRefreshToken(testSecret, 3600, 42, "prod", 0) require.NoError(t, err) t.Run("valid token", func(t *testing.T) { claims, err := ParseRefreshToken(validToken, testSecret) require.NoError(t, err) assert.Equal(t, int64(42), claims.UserId) assert.Equal(t, "prod", claims.ProductCode) }) t.Run("wrong secret", func(t *testing.T) { _, err := ParseRefreshToken(validToken, "wrong-secret") assert.Error(t, err) }) t.Run("invalid token string", func(t *testing.T) { _, err := ParseRefreshToken("not-a-valid-token", testSecret) assert.Error(t, err) }) t.Run("empty token", func(t *testing.T) { _, err := ParseRefreshToken("", testSecret) assert.Error(t, err) }) t.Run("expired token", func(t *testing.T) { expiredToken, err := GenerateRefreshToken(testSecret, 1, 1, "p", 0) require.NoError(t, err) time.Sleep(2 * time.Second) _, err = ParseRefreshToken(expiredToken, testSecret) assert.Error(t, err) }) // TC-0305: AccessToken误用 — TokenType校验拒绝 t.Run("access token used as refresh - should be rejected", func(t *testing.T) { accessToken, err := GenerateAccessToken(testSecret, 3600, 1, "u", "p", "M", 0) require.NoError(t, err) _, err = ParseRefreshToken(accessToken, testSecret) assert.Error(t, err, "BUG-002: access token 不应被 ParseRefreshToken 接受,应通过 TokenType 字段区分") }) } // TC-0294: secret="" func TestGenerateAccessToken_EmptySecret(t *testing.T) { tokenStr, err := GenerateAccessToken("", 3600, 1, "u", "p", "M", 0) require.NoError(t, err) assert.NotEmpty(t, tokenStr) token, err := jwt.ParseWithClaims(tokenStr, &middleware.Claims{}, func(token *jwt.Token) (interface{}, error) { return []byte(""), nil }) require.NoError(t, err) assert.True(t, token.Valid) claims, ok := token.Claims.(*middleware.Claims) require.True(t, ok) assert.Equal(t, int64(1), claims.UserId) }