feat: 实现 Phase 3 JWT + 认证服务 #95
127
backend/internal/auth/jwt_test.go
Normal file
127
backend/internal/auth/jwt_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGeneratePair_ReturnsNonEmptyTokens(t *testing.T) {
|
||||
tm := NewTokenManager("test-secret-key", 15*time.Minute, 7*24*time.Hour)
|
||||
|
||||
access, refresh, err := tm.GeneratePair("user-123", "alice")
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, access)
|
||||
assert.NotEmpty(t, refresh)
|
||||
assert.NotEqual(t, access, refresh)
|
||||
}
|
||||
|
||||
func TestValidateAccess_ValidToken(t *testing.T) {
|
||||
tm := NewTokenManager("test-secret-key", 15*time.Minute, 7*24*time.Hour)
|
||||
|
||||
access, _, err := tm.GeneratePair("user-123", "alice")
|
||||
require.NoError(t, err)
|
||||
|
||||
claims, err := tm.ValidateAccess(access)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "user-123", claims.UserID)
|
||||
assert.Equal(t, "alice", claims.Username)
|
||||
assert.Equal(t, "camtalk", claims.Issuer)
|
||||
}
|
||||
|
||||
func TestValidateRefresh_ValidToken(t *testing.T) {
|
||||
tm := NewTokenManager("test-secret-key", 15*time.Minute, 7*24*time.Hour)
|
||||
|
||||
_, refresh, err := tm.GeneratePair("user-123", "alice")
|
||||
require.NoError(t, err)
|
||||
|
||||
claims, err := tm.ValidateRefresh(refresh)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "user-123", claims.UserID)
|
||||
assert.Equal(t, "alice", claims.Username)
|
||||
assert.NotEmpty(t, claims.ID) // refresh token 应含唯一 ID
|
||||
}
|
||||
|
||||
func TestValidateAccess_ExpiredToken(t *testing.T) {
|
||||
// 使用极短的 TTL
|
||||
tm := NewTokenManager("test-secret-key", -1*time.Second, -1*time.Second)
|
||||
|
||||
access, _, err := tm.GeneratePair("user-123", "alice")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = tm.ValidateAccess(access)
|
||||
assert.ErrorIs(t, err, ErrInvalidToken)
|
||||
}
|
||||
|
||||
func TestValidateAccess_WrongSecret(t *testing.T) {
|
||||
tm1 := NewTokenManager("secret-1", 15*time.Minute, 7*24*time.Hour)
|
||||
tm2 := NewTokenManager("secret-2", 15*time.Minute, 7*24*time.Hour)
|
||||
|
||||
access, _, err := tm1.GeneratePair("user-123", "alice")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = tm2.ValidateAccess(access)
|
||||
assert.ErrorIs(t, err, ErrInvalidToken)
|
||||
}
|
||||
|
||||
func TestValidateAccess_InvalidFormat(t *testing.T) {
|
||||
tm := NewTokenManager("test-secret-key", 15*time.Minute, 7*24*time.Hour)
|
||||
|
||||
_, err := tm.ValidateAccess("not-a-valid-token")
|
||||
assert.ErrorIs(t, err, ErrInvalidToken)
|
||||
}
|
||||
|
||||
func TestValidateAccess_EmptyString(t *testing.T) {
|
||||
tm := NewTokenManager("test-secret-key", 15*time.Minute, 7*24*time.Hour)
|
||||
|
||||
_, err := tm.ValidateAccess("")
|
||||
assert.ErrorIs(t, err, ErrInvalidToken)
|
||||
}
|
||||
|
||||
func TestHashToken_Deterministic(t *testing.T) {
|
||||
hash1 := HashToken("some-token-value")
|
||||
hash2 := HashToken("some-token-value")
|
||||
assert.Equal(t, hash1, hash2)
|
||||
assert.Len(t, hash1, 64) // SHA256 hex = 64 chars
|
||||
}
|
||||
|
||||
func TestHashToken_DifferentInputsDifferentHashes(t *testing.T) {
|
||||
hash1 := HashToken("token-a")
|
||||
hash2 := HashToken("token-b")
|
||||
assert.NotEqual(t, hash1, hash2)
|
||||
}
|
||||
|
||||
func TestValidateRefresh_ExpiredToken(t *testing.T) {
|
||||
tm := NewTokenManager("test-secret-key", -1*time.Minute, -1*time.Minute)
|
||||
|
||||
_, refresh, err := tm.GeneratePair("user-123", "alice")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = tm.ValidateRefresh(refresh)
|
||||
assert.ErrorIs(t, err, ErrInvalidToken)
|
||||
}
|
||||
|
||||
func TestGeneratePair_TokenClaimsContainCorrectExpiry(t *testing.T) {
|
||||
accessTTL := 15 * time.Minute
|
||||
refreshTTL := 7 * 24 * time.Hour
|
||||
tm := NewTokenManager("test-secret-key", accessTTL, refreshTTL)
|
||||
|
||||
before := time.Now()
|
||||
access, refresh, err := tm.GeneratePair("user-123", "alice")
|
||||
require.NoError(t, err)
|
||||
after := time.Now()
|
||||
|
||||
// 校验 access token 有效期范围
|
||||
accessClaims, err := tm.ValidateAccess(access)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, accessClaims.ExpiresAt.Time.After(before.Add(accessTTL).Add(-1*time.Second)))
|
||||
assert.True(t, accessClaims.ExpiresAt.Time.Before(after.Add(accessTTL).Add(1*time.Second)))
|
||||
|
||||
// 校验 refresh token 有效期范围
|
||||
refreshClaims, err := tm.ValidateRefresh(refresh)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, refreshClaims.ExpiresAt.Time.After(before.Add(refreshTTL).Add(-1*time.Second)))
|
||||
assert.True(t, refreshClaims.ExpiresAt.Time.Before(after.Add(refreshTTL).Add(1*time.Second)))
|
||||
}
|
||||
Reference in New Issue
Block a user