Files
Neo-ZQYY/apps/backend/tests/test_auth_jwt.py

148 lines
4.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
JWT 认证模块单元测试。
覆盖:令牌生成、验证、过期、类型校验、密码哈希、依赖注入。
"""
import os
import time
import pytest
from jose import jwt as jose_jwt
# 测试前设置 JWT_SECRET_KEY避免空密钥
os.environ.setdefault("JWT_SECRET_KEY", "test-secret-key-for-unit-tests")
from app.auth.jwt import (
create_access_token,
create_refresh_token,
create_token_pair,
decode_access_token,
decode_refresh_token,
decode_token,
hash_password,
verify_password,
)
from app import config
# ---------------------------------------------------------------------------
# 密码哈希
# ---------------------------------------------------------------------------
class TestPasswordHashing:
def test_hash_and_verify(self):
raw = "my_secure_password"
hashed = hash_password(raw)
assert verify_password(raw, hashed)
def test_wrong_password_rejected(self):
hashed = hash_password("correct")
assert not verify_password("wrong", hashed)
def test_hash_is_not_plaintext(self):
raw = "plaintext123"
hashed = hash_password(raw)
assert hashed != raw
# ---------------------------------------------------------------------------
# 令牌生成与解码
# ---------------------------------------------------------------------------
class TestTokenCreation:
def test_access_token_contains_expected_fields(self):
token = create_access_token(user_id=1, site_id=100)
payload = decode_token(token)
assert payload["sub"] == "1"
assert payload["site_id"] == 100
assert payload["type"] == "access"
assert "exp" in payload
def test_refresh_token_contains_expected_fields(self):
token = create_refresh_token(user_id=2, site_id=200)
payload = decode_token(token)
assert payload["sub"] == "2"
assert payload["site_id"] == 200
assert payload["type"] == "refresh"
assert "exp" in payload
def test_token_pair_returns_both_tokens(self):
pair = create_token_pair(user_id=3, site_id=300)
assert "access_token" in pair
assert "refresh_token" in pair
assert pair["token_type"] == "bearer"
# 验证两个令牌类型不同
access_payload = decode_token(pair["access_token"])
refresh_payload = decode_token(pair["refresh_token"])
assert access_payload["type"] == "access"
assert refresh_payload["type"] == "refresh"
# ---------------------------------------------------------------------------
# 令牌类型校验
# ---------------------------------------------------------------------------
class TestTokenTypeValidation:
def test_decode_access_token_rejects_refresh(self):
"""access 解码器拒绝 refresh 令牌。"""
token = create_refresh_token(user_id=1, site_id=1)
with pytest.raises(Exception):
decode_access_token(token)
def test_decode_refresh_token_rejects_access(self):
"""refresh 解码器拒绝 access 令牌。"""
token = create_access_token(user_id=1, site_id=1)
with pytest.raises(Exception):
decode_refresh_token(token)
def test_decode_access_token_accepts_access(self):
token = create_access_token(user_id=5, site_id=50)
payload = decode_access_token(token)
assert payload["sub"] == "5"
assert payload["site_id"] == 50
def test_decode_refresh_token_accepts_refresh(self):
token = create_refresh_token(user_id=6, site_id=60)
payload = decode_refresh_token(token)
assert payload["sub"] == "6"
assert payload["site_id"] == 60
# ---------------------------------------------------------------------------
# 令牌过期
# ---------------------------------------------------------------------------
class TestTokenExpiry:
def test_expired_token_rejected(self):
"""手动构造已过期令牌,验证解码失败。"""
payload = {
"sub": "1",
"site_id": 1,
"type": "access",
"exp": int(time.time()) - 10, # 10 秒前过期
}
token = jose_jwt.encode(
payload, config.JWT_SECRET_KEY, algorithm=config.JWT_ALGORITHM
)
with pytest.raises(Exception):
decode_token(token)
# ---------------------------------------------------------------------------
# 无效令牌
# ---------------------------------------------------------------------------
class TestInvalidToken:
def test_garbage_token_rejected(self):
with pytest.raises(Exception):
decode_token("not.a.valid.jwt")
def test_wrong_secret_rejected(self):
"""用不同密钥签发的令牌应被拒绝。"""
payload = {"sub": "1", "site_id": 1, "type": "access", "exp": int(time.time()) + 3600}
token = jose_jwt.encode(payload, "wrong-secret", algorithm="HS256")
with pytest.raises(Exception):
decode_token(token)