239 lines
8.1 KiB
Python
239 lines
8.1 KiB
Python
# 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
|