# 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