jiachenlong/backend/tests/test_auth.py

239 lines
8.1 KiB
Python
Raw Normal View History

# tests/test_auth.py - 认证模块单元测试
import pytest
from app.core.auth import (
verify_password,
get_password_hash,
create_access_token,
decode_access_token,
)
class TestPasswordHashing:
"""密码哈希与验证测试"""
def test_hash_password_returns_string(self):
"""哈希密码应返回字符串"""
password = "testpassword123"
hashed = get_password_hash(password)
assert isinstance(hashed, str)
assert hashed != password
def test_hash_password_different_each_time(self):
"""每次哈希应不同bcrypt使用随机盐"""
password = "testpassword123"
hash1 = get_password_hash(password)
hash2 = get_password_hash(password)
assert hash1 != hash2
def test_verify_password_correct(self):
"""正确密码应验证通过"""
password = "testpassword123"
hashed = get_password_hash(password)
assert verify_password(password, hashed) is True
def test_verify_password_incorrect(self):
"""错误密码应验证失败"""
password = "testpassword123"
wrong_password = "wrongpassword456"
hashed = get_password_hash(password)
assert verify_password(wrong_password, hashed) is False
def test_verify_password_empty(self):
"""空密码应验证失败"""
password = "testpassword123"
hashed = get_password_hash(password)
assert verify_password("", hashed) is False
def test_verify_password_none(self):
"""None密码应验证失败"""
password = "testpassword123"
hashed = get_password_hash(password)
assert verify_password(None, hashed) is False
class TestJWTToken:
"""JWT令牌创建与解析测试"""
def test_create_and_decode_token(self):
"""创建令牌后能正确解码"""
data = {"sub": "user123", "role": "admin"}
token = create_access_token(data)
assert isinstance(token, str)
assert len(token) > 0
payload = decode_access_token(token)
assert payload is not None
assert payload["sub"] == "user123"
assert payload["role"] == "admin"
def test_decode_invalid_token_returns_none(self):
"""无效令牌应返回None"""
invalid_token = "invalid.token.here"
payload = decode_access_token(invalid_token)
assert payload is None
def test_decode_empty_token_returns_none(self):
"""空令牌应返回None"""
payload = decode_access_token("")
assert payload is None
def test_token_contains_expiration(self):
"""令牌应包含过期时间"""
data = {"sub": "user123"}
token = create_access_token(data)
payload = decode_access_token(token)
assert "exp" in payload
class TestAuthEndpoints:
"""认证API端点测试"""
def test_register_success(self, client, sample_user_data):
"""用户注册成功"""
response = client.post("/api/auth/register", json=sample_user_data)
assert response.status_code == 200
data = response.json()
assert data["username"] == sample_user_data["f01_01_name"]
assert "id" in data
assert "user_code" in data
def test_register_duplicate_username(self, client, sample_user_data, registered_user):
"""重复用户名应注册失败"""
response = client.post("/api/auth/register", json=sample_user_data)
assert response.status_code == 400
assert "已存在" in response.json().get("detail", "")
def test_register_short_password(self, client, sample_user_data):
"""密码过短应注册失败"""
sample_user_data["password"] = "123" # 小于6位
response = client.post("/api/auth/register", json=sample_user_data)
assert response.status_code == 422 # Pydantic验证失败
def test_login_success(self, client, sample_user_data):
"""登录成功返回token"""
# 先注册
client.post("/api/auth/register", json=sample_user_data)
# 再登录
response = client.post(
"/api/auth/login",
data={
"username": sample_user_data["f01_01_name"],
"password": sample_user_data["password"],
},
)
assert response.status_code == 200
data = response.json()
assert "access_token" in data
assert data["token_type"] == "bearer"
def test_login_with_user_code(self, client, sample_user_data):
"""使用用户编码登录"""
# 先注册
client.post("/api/auth/register", json=sample_user_data)
# 获取用户编码
user_response = client.post("/api/auth/register", json=sample_user_data)
registered_user_response = client.post(
"/api/auth/login",
data={
"username": sample_user_data["f01_01_name"],
"password": sample_user_data["password"],
},
)
token = registered_user_response.json()["access_token"]
me_response = client.get("/api/auth/me", headers={"Authorization": f"Bearer {token}"})
user_code = me_response.json()["user_code"]
# 用用户编码登录
login_response = client.post(
"/api/auth/login",
data={
"username": user_code,
"password": sample_user_data["password"],
},
)
assert login_response.status_code == 200
assert "access_token" in login_response.json()
def test_login_wrong_password(self, client, sample_user_data):
"""错误密码应登录失败"""
# 先注册
client.post("/api/auth/register", json=sample_user_data)
response = client.post(
"/api/auth/login",
data={
"username": sample_user_data["f01_01_name"],
"password": "wrongpassword",
},
)
assert response.status_code == 401
def test_login_nonexistent_user(self, client):
"""不存在的用户应登录失败"""
response = client.post(
"/api/auth/login",
data={
"username": "nonexistent",
"password": "somepassword",
},
)
assert response.status_code == 401
def test_get_current_user_success(self, client, auth_headers):
"""获取当前用户信息成功"""
response = client.get("/api/auth/me", headers=auth_headers)
assert response.status_code == 200
data = response.json()
assert "username" in data
assert "user_code" in data
def test_get_current_user_no_token(self, client):
"""无令牌应返回401"""
response = client.get("/api/auth/me")
assert response.status_code == 401
def test_get_current_user_invalid_token(self, client):
"""无效令牌应返回401"""
response = client.get(
"/api/auth/me",
headers={"Authorization": "Bearer invalid-token"},
)
assert response.status_code == 401
def test_change_password_success(self, client, auth_headers, sample_user_data):
"""修改密码成功"""
response = client.post(
"/api/auth/change-password",
headers=auth_headers,
json={
"old_password": sample_user_data["password"],
"new_password": "newpassword456",
},
)
assert response.status_code == 200
# 用新密码登录应该成功
login_response = client.post(
"/api/auth/login",
data={
"username": sample_user_data["f01_01_name"],
"password": "newpassword456",
},
)
assert login_response.status_code == 200
def test_change_password_wrong_old_password(self, client, auth_headers, sample_user_data):
"""旧密码错误应修改失败"""
response = client.post(
"/api/auth/change-password",
headers=auth_headers,
json={
"old_password": "wrongoldpassword",
"new_password": "newpassword456",
},
)
assert response.status_code == 400