From d73837e5ef7185566cf9f5b1b48d2d37a986e414 Mon Sep 17 00:00:00 2001 From: long-agent Date: Thu, 9 Apr 2026 09:55:55 +0800 Subject: [PATCH] test: add comprehensive JWT coverage tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added tests for JWT token operations: - TestGenerateAccessToken_Success - TestGenerateRefreshToken_Success - TestGenerateTokenPair_Success - TestGenerateTokenPairWithRemember_Success - TestValidateAccessToken_WrongType - TestValidateRefreshToken_WrongType - TestValidateAccessToken_InvalidToken - TestGetAccessTokenExpire - TestGetRefreshTokenExpire - TestParseToken_Invalid - TestGenerateLongLivedRefreshToken_Success - TestParseRSAPrivateKey_InvalidPEM - TestParseRSAPublicKey_InvalidPEM - TestGenerateAndPersistRSAKeyPair_EmptyPath - TestRefreshAccessToken_Success - TestRefreshAccessToken_InvalidRefreshToken - TestRefreshAccessToken_AccessTokenProvided auth module coverage: 23.8% → 52.5% Key functions now at 100%: ValidateAccessToken, ValidateRefreshToken, RefreshAccessToken, GetAccessTokenExpire, GetRefreshTokenExpire --- internal/auth/jwt_password_test.go | 384 +++++++++++++++++++++++++++++ 1 file changed, 384 insertions(+) diff --git a/internal/auth/jwt_password_test.go b/internal/auth/jwt_password_test.go index 7a22bff..3f6187b 100644 --- a/internal/auth/jwt_password_test.go +++ b/internal/auth/jwt_password_test.go @@ -124,3 +124,387 @@ func TestNewJWTWithOptions_RS256_RequireExistingKeysAllowsExistingFiles(t *testi t.Fatalf("unexpected algorithm: %s", jwtManager.GetAlgorithm()) } } + +func TestGenerateAccessToken_Success(t *testing.T) { + jwtManager, err := NewJWTWithOptions(JWTOptions{ + Algorithm: jwtAlgorithmHS256, + HS256Secret: "test-secret-key-for-jwt-at-least-32-chars", + AccessTokenExpire: 15 * time.Minute, + RefreshTokenExpire: 7 * 24 * time.Hour, + }) + if err != nil { + t.Fatalf("create jwt manager failed: %v", err) + } + + token, err := jwtManager.GenerateAccessToken(123, "testuser") + if err != nil { + t.Fatalf("generate access token failed: %v", err) + } + if token == "" { + t.Fatal("expected non-empty token") + } + + claims, err := jwtManager.ValidateAccessToken(token) + if err != nil { + t.Fatalf("validate access token failed: %v", err) + } + if claims.UserID != 123 { + t.Errorf("UserID = %d, want 123", claims.UserID) + } + if claims.Username != "testuser" { + t.Errorf("Username = %s, want testuser", claims.Username) + } + if claims.Type != "access" { + t.Errorf("Type = %s, want access", claims.Type) + } +} + +func TestGenerateRefreshToken_Success(t *testing.T) { + jwtManager, err := NewJWTWithOptions(JWTOptions{ + Algorithm: jwtAlgorithmHS256, + HS256Secret: "test-secret-key-for-jwt-at-least-32-chars", + AccessTokenExpire: 15 * time.Minute, + RefreshTokenExpire: 7 * 24 * time.Hour, + }) + if err != nil { + t.Fatalf("create jwt manager failed: %v", err) + } + + token, err := jwtManager.GenerateRefreshToken(456, "refreshuser") + if err != nil { + t.Fatalf("generate refresh token failed: %v", err) + } + if token == "" { + t.Fatal("expected non-empty token") + } + + claims, err := jwtManager.ValidateRefreshToken(token) + if err != nil { + t.Fatalf("validate refresh token failed: %v", err) + } + if claims.UserID != 456 { + t.Errorf("UserID = %d, want 456", claims.UserID) + } + if claims.Type != "refresh" { + t.Errorf("Type = %s, want refresh", claims.Type) + } +} + +func TestGenerateTokenPair_Success(t *testing.T) { + jwtManager, err := NewJWTWithOptions(JWTOptions{ + Algorithm: jwtAlgorithmHS256, + HS256Secret: "test-secret-key-for-jwt-at-least-32-chars", + AccessTokenExpire: 15 * time.Minute, + RefreshTokenExpire: 7 * 24 * time.Hour, + }) + if err != nil { + t.Fatalf("create jwt manager failed: %v", err) + } + + accessToken, refreshToken, err := jwtManager.GenerateTokenPair(789, "pairuser") + if err != nil { + t.Fatalf("generate token pair failed: %v", err) + } + if accessToken == "" || refreshToken == "" { + t.Fatal("expected non-empty tokens") + } + + accessClaims, err := jwtManager.ValidateAccessToken(accessToken) + if err != nil { + t.Fatalf("validate access token failed: %v", err) + } + if accessClaims.UserID != 789 { + t.Errorf("UserID = %d, want 789", accessClaims.UserID) + } + + refreshClaims, err := jwtManager.ValidateRefreshToken(refreshToken) + if err != nil { + t.Fatalf("validate refresh token failed: %v", err) + } + if refreshClaims.UserID != 789 { + t.Errorf("UserID = %d, want 789", refreshClaims.UserID) + } +} + +func TestGenerateTokenPairWithRemember_Success(t *testing.T) { + jwtManager, err := NewJWTWithOptions(JWTOptions{ + Algorithm: jwtAlgorithmHS256, + HS256Secret: "test-secret-key-for-jwt-at-least-32-chars", + AccessTokenExpire: 15 * time.Minute, + RefreshTokenExpire: 7 * 24 * time.Hour, + RememberLoginExpire: 30 * 24 * time.Hour, + }) + if err != nil { + t.Fatalf("create jwt manager failed: %v", err) + } + + accessToken, refreshToken, err := jwtManager.GenerateTokenPairWithRemember(999, "rememberuser", true) + if err != nil { + t.Fatalf("generate token pair with remember failed: %v", err) + } + if accessToken == "" || refreshToken == "" { + t.Fatal("expected non-empty tokens") + } + + accessClaims, err := jwtManager.ValidateAccessToken(accessToken) + if err != nil { + t.Fatalf("validate access token failed: %v", err) + } + if accessClaims.Remember { + t.Error("access token should not have Remember flag") + } + + refreshClaims, err := jwtManager.ValidateRefreshToken(refreshToken) + if err != nil { + t.Fatalf("validate refresh token failed: %v", err) + } + if !refreshClaims.Remember { + t.Error("refresh token should have Remember flag set to true") + } +} + +func TestValidateAccessToken_WrongType(t *testing.T) { + jwtManager, err := NewJWTWithOptions(JWTOptions{ + Algorithm: jwtAlgorithmHS256, + HS256Secret: "test-secret-key-for-jwt-at-least-32-chars", + AccessTokenExpire: 15 * time.Minute, + RefreshTokenExpire: 7 * 24 * time.Hour, + }) + if err != nil { + t.Fatalf("create jwt manager failed: %v", err) + } + + // Use a refresh token as if it were an access token + refreshToken, err := jwtManager.GenerateRefreshToken(123, "testuser") + if err != nil { + t.Fatalf("generate refresh token failed: %v", err) + } + + _, err = jwtManager.ValidateAccessToken(refreshToken) + if err == nil { + t.Fatal("expected error when validating refresh token as access token") + } +} + +func TestValidateRefreshToken_WrongType(t *testing.T) { + jwtManager, err := NewJWTWithOptions(JWTOptions{ + Algorithm: jwtAlgorithmHS256, + HS256Secret: "test-secret-key-for-jwt-at-least-32-chars", + AccessTokenExpire: 15 * time.Minute, + RefreshTokenExpire: 7 * 24 * time.Hour, + }) + if err != nil { + t.Fatalf("create jwt manager failed: %v", err) + } + + // Use an access token as if it were a refresh token + accessToken, err := jwtManager.GenerateAccessToken(123, "testuser") + if err != nil { + t.Fatalf("generate access token failed: %v", err) + } + + _, err = jwtManager.ValidateRefreshToken(accessToken) + if err == nil { + t.Fatal("expected error when validating access token as refresh token") + } +} + +func TestValidateAccessToken_InvalidToken(t *testing.T) { + jwtManager, err := NewJWTWithOptions(JWTOptions{ + Algorithm: jwtAlgorithmHS256, + HS256Secret: "test-secret-key-for-jwt-at-least-32-chars", + AccessTokenExpire: 15 * time.Minute, + RefreshTokenExpire: 7 * 24 * time.Hour, + }) + if err != nil { + t.Fatalf("create jwt manager failed: %v", err) + } + + _, err = jwtManager.ValidateAccessToken("invalid-token") + if err == nil { + t.Fatal("expected error for invalid token") + } +} + +func TestGetAccessTokenExpire(t *testing.T) { + jwtManager, err := NewJWTWithOptions(JWTOptions{ + Algorithm: jwtAlgorithmHS256, + HS256Secret: "test-secret-key-for-jwt-at-least-32-chars", + AccessTokenExpire: 30 * time.Minute, + RefreshTokenExpire: 7 * 24 * time.Hour, + }) + if err != nil { + t.Fatalf("create jwt manager failed: %v", err) + } + + expire := jwtManager.GetAccessTokenExpire() + if expire != 30*time.Minute { + t.Errorf("GetAccessTokenExpire() = %v, want 30m", expire) + } +} + +func TestGetRefreshTokenExpire(t *testing.T) { + jwtManager, err := NewJWTWithOptions(JWTOptions{ + Algorithm: jwtAlgorithmHS256, + HS256Secret: "test-secret-key-for-jwt-at-least-32-chars", + AccessTokenExpire: 15 * time.Minute, + RefreshTokenExpire: 14 * 24 * time.Hour, + }) + if err != nil { + t.Fatalf("create jwt manager failed: %v", err) + } + + expire := jwtManager.GetRefreshTokenExpire() + if expire != 14*24*time.Hour { + t.Errorf("GetRefreshTokenExpire() = %v, want 14d", expire) + } +} + +func TestParseToken_Invalid(t *testing.T) { + jwtManager, err := NewJWTWithOptions(JWTOptions{ + Algorithm: jwtAlgorithmHS256, + HS256Secret: "test-secret-key-for-jwt-at-least-32-chars", + AccessTokenExpire: 15 * time.Minute, + RefreshTokenExpire: 7 * 24 * time.Hour, + }) + if err != nil { + t.Fatalf("create jwt manager failed: %v", err) + } + + _, err = jwtManager.ParseToken("not-a-valid-jwt-token") + if err == nil { + t.Fatal("expected error for invalid token") + } +} + +func TestGenerateLongLivedRefreshToken_Success(t *testing.T) { + jwtManager, err := NewJWTWithOptions(JWTOptions{ + Algorithm: jwtAlgorithmHS256, + HS256Secret: "test-secret-key-for-jwt-at-least-32-chars", + AccessTokenExpire: 15 * time.Minute, + RefreshTokenExpire: 7 * 24 * time.Hour, + RememberLoginExpire: 30 * 24 * time.Hour, + }) + if err != nil { + t.Fatalf("create jwt manager failed: %v", err) + } + + token, err := jwtManager.GenerateLongLivedRefreshToken(123, "longliveuser") + if err != nil { + t.Fatalf("generate long lived refresh token failed: %v", err) + } + if token == "" { + t.Fatal("expected non-empty token") + } + + claims, err := jwtManager.ValidateRefreshToken(token) + if err != nil { + t.Fatalf("validate refresh token failed: %v", err) + } + if claims.UserID != 123 { + t.Errorf("UserID = %d, want 123", claims.UserID) + } + if !claims.Remember { + t.Error("expected Remember flag to be set") + } +} + +func TestParseRSAPrivateKey_InvalidPEM(t *testing.T) { + _, err := parseRSAPrivateKey("not-a-valid-pem-block") + if err == nil { + t.Fatal("expected error for invalid PEM") + } +} + +func TestParseRSAPublicKey_InvalidPEM(t *testing.T) { + _, err := parseRSAPublicKey("not-a-valid-pem-block") + if err == nil { + t.Fatal("expected error for invalid PEM") + } +} + +func TestGenerateAndPersistRSAKeyPair_EmptyPath(t *testing.T) { + _, _, err := generateAndPersistRSAKeyPair("", "public.pem") + if err == nil { + t.Fatal("expected error for empty private path") + } + _, _, err = generateAndPersistRSAKeyPair("private.pem", "") + if err == nil { + t.Fatal("expected error for empty public path") + } +} + +func TestRefreshAccessToken_Success(t *testing.T) { + jwtManager, err := NewJWTWithOptions(JWTOptions{ + Algorithm: jwtAlgorithmHS256, + HS256Secret: "test-secret-key-for-jwt-at-least-32-chars", + AccessTokenExpire: 15 * time.Minute, + RefreshTokenExpire: 7 * 24 * time.Hour, + }) + if err != nil { + t.Fatalf("create jwt manager failed: %v", err) + } + + // Generate a valid refresh token first + refreshToken, err := jwtManager.GenerateRefreshToken(123, "testuser") + if err != nil { + t.Fatalf("generate refresh token failed: %v", err) + } + + // Use refresh to get new access token + newAccessToken, err := jwtManager.RefreshAccessToken(refreshToken) + if err != nil { + t.Fatalf("refresh access token failed: %v", err) + } + if newAccessToken == "" { + t.Fatal("expected non-empty access token") + } + + claims, err := jwtManager.ValidateAccessToken(newAccessToken) + if err != nil { + t.Fatalf("validate new access token failed: %v", err) + } + if claims.UserID != 123 { + t.Errorf("UserID = %d, want 123", claims.UserID) + } +} + +func TestRefreshAccessToken_InvalidRefreshToken(t *testing.T) { + jwtManager, err := NewJWTWithOptions(JWTOptions{ + Algorithm: jwtAlgorithmHS256, + HS256Secret: "test-secret-key-for-jwt-at-least-32-chars", + AccessTokenExpire: 15 * time.Minute, + RefreshTokenExpire: 7 * 24 * time.Hour, + }) + if err != nil { + t.Fatalf("create jwt manager failed: %v", err) + } + + _, err = jwtManager.RefreshAccessToken("invalid-refresh-token") + if err == nil { + t.Fatal("expected error for invalid refresh token") + } +} + +func TestRefreshAccessToken_AccessTokenProvided(t *testing.T) { + jwtManager, err := NewJWTWithOptions(JWTOptions{ + Algorithm: jwtAlgorithmHS256, + HS256Secret: "test-secret-key-for-jwt-at-least-32-chars", + AccessTokenExpire: 15 * time.Minute, + RefreshTokenExpire: 7 * 24 * time.Hour, + }) + if err != nil { + t.Fatalf("create jwt manager failed: %v", err) + } + + // Generate an access token and try to use it as refresh + accessToken, err := jwtManager.GenerateAccessToken(123, "testuser") + if err != nil { + t.Fatalf("generate access token failed: %v", err) + } + + _, err = jwtManager.RefreshAccessToken(accessToken) + if err == nil { + t.Fatal("expected error when using access token as refresh token") + } +}