""" 认证 API 集成测试 测试注册、登录、获取当前用户、刷新 Token 等端点 使用同步 TestClient + Mock 数据库会话 """ from datetime import datetime from unittest.mock import AsyncMock, MagicMock, patch from fastapi.testclient import TestClient from app.core.security import hash_password from app.models.user import User, UserStatus from tests.conftest import make_db_result class TestAuthAPI: """认证 API 集成测试""" def test_register_user_success(self, client: TestClient, mock_db: AsyncMock): """测试用户注册成功""" # mock: 邮箱不存在 mock_db.execute = AsyncMock( return_value=make_db_result(scalar=None) ) # mock: commit + refresh 后返回带 id 的 user def refresh_side_effect(obj, *args, **kwargs): obj.id = 1 obj.created_at = datetime.utcnow() obj.updated_at = datetime.utcnow() obj.last_login_at = None obj.permissions = None obj.status = UserStatus.ACTIVE.value mock_db.refresh.side_effect = refresh_side_effect response = client.post( "/api/auth/register", json={ "email": "newuser@example.com", "password": "password123", "full_name": "新用户", "company_id": 1, }, ) assert response.status_code == 201 data = response.json() assert data["email"] == "newuser@example.com" assert data["full_name"] == "新用户" def test_register_duplicate_email(self, client: TestClient, mock_db: AsyncMock): """测试重复邮箱注册失败""" existing_user = MagicMock() mock_db.execute = AsyncMock( return_value=make_db_result(scalar=existing_user) ) response = client.post( "/api/auth/register", json={ "email": "dup@example.com", "password": "password123", "full_name": "用户1", "company_id": 1, }, ) assert response.status_code == 400 def test_login_success(self, client: TestClient, mock_db: AsyncMock): """测试登录成功""" password = "pass123456" user = User( id=1, company_id=1, email="login@example.com", hashed_password=hash_password(password), full_name="登录用户", role="会计", status=UserStatus.ACTIVE.value, created_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) mock_db.execute = AsyncMock( return_value=make_db_result(scalar=user) ) response = client.post( "/api/auth/login", json={"email": "login@example.com", "password": password}, ) assert response.status_code == 200 data = response.json() assert "access_token" in data assert data["token_type"] == "bearer" assert data["user"]["email"] == "login@example.com" def test_login_wrong_password(self, client: TestClient, mock_db: AsyncMock): """测试密码错误登录失败""" user = User( id=1, company_id=1, email="wrong@example.com", hashed_password=hash_password("correctpass"), full_name="用户", status=UserStatus.ACTIVE.value, created_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) mock_db.execute = AsyncMock( return_value=make_db_result(scalar=user) ) response = client.post( "/api/auth/login", json={"email": "wrong@example.com", "password": "wrongpass"}, ) assert response.status_code == 401 def test_login_nonexistent_user(self, client: TestClient, mock_db: AsyncMock): """测试不存在的用户登录失败""" mock_db.execute = AsyncMock( return_value=make_db_result(scalar=None) ) response = client.post( "/api/auth/login", json={"email": "nobody@example.com", "password": "anypass"}, ) assert response.status_code == 401 def test_get_current_user_no_token(self, client: TestClient): """测试无 Token 访问被拒""" response = client.get("/api/auth/me") assert response.status_code == 401 def test_refresh_token(self, client: TestClient, auth_headers: dict, mock_db: AsyncMock): """测试刷新 Token""" user = User( id=1, company_id=1, email="test@example.com", hashed_password="x", full_name="测试用户", status=UserStatus.ACTIVE.value, created_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) mock_db.execute = AsyncMock( return_value=make_db_result(scalar=user) ) response = client.post("/api/auth/refresh", headers=auth_headers) assert response.status_code == 200 data = response.json() assert "access_token" in data def test_logout(self, client: TestClient, auth_headers: dict, mock_db: AsyncMock): """测试登出""" user = User( id=1, company_id=1, email="test@example.com", hashed_password="x", full_name="测试用户", status=UserStatus.ACTIVE.value, created_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) mock_db.execute = AsyncMock( return_value=make_db_result(scalar=user) ) response = client.post("/api/auth/logout", headers=auth_headers) assert response.status_code == 200