249 lines
8.7 KiB
Python
249 lines
8.7 KiB
Python
|
|
"""用户管理服务 - 基础单元测试模块。
|
|||
|
|
|
|||
|
|
使用 pytest 和 httpx 对 API 接口进行测试。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import pytest
|
|||
|
|
from fastapi.testclient import TestClient
|
|||
|
|
|
|||
|
|
from app.main import app
|
|||
|
|
from app.database import db
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ──────────────────────────────────────────────
|
|||
|
|
# 测试客户端
|
|||
|
|
# ──────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
@pytest.fixture(autouse=True)
|
|||
|
|
def reset_database():
|
|||
|
|
"""每个测试用例执行前重置数据库状态。
|
|||
|
|
|
|||
|
|
保存原始数据并在测试后恢复,避免测试间的数据污染。
|
|||
|
|
"""
|
|||
|
|
# 清空数据库并重新初始化种子数据
|
|||
|
|
db._users.clear()
|
|||
|
|
db._username_index.clear()
|
|||
|
|
db._init_seed_data()
|
|||
|
|
yield
|
|||
|
|
|
|||
|
|
|
|||
|
|
client = TestClient(app)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ──────────────────────────────────────────────
|
|||
|
|
# 健康检查测试
|
|||
|
|
# ──────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TestHealthCheck:
|
|||
|
|
"""健康检查接口测试类。"""
|
|||
|
|
|
|||
|
|
def test_health_check_returns_ok(self):
|
|||
|
|
"""测试健康检查接口返回状态 ok。"""
|
|||
|
|
response = client.get("/health")
|
|||
|
|
assert response.status_code == 200
|
|||
|
|
data = response.json()
|
|||
|
|
assert data["status"] == "ok"
|
|||
|
|
assert data["service"] == "user-management-service"
|
|||
|
|
assert data["total_users"] >= 3
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ──────────────────────────────────────────────
|
|||
|
|
# 用户创建测试
|
|||
|
|
# ──────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TestCreateUser:
|
|||
|
|
"""用户注册接口测试类。"""
|
|||
|
|
|
|||
|
|
def test_create_user_success(self):
|
|||
|
|
"""测试成功创建用户。"""
|
|||
|
|
response = client.post(
|
|||
|
|
"/api/users",
|
|||
|
|
json={
|
|||
|
|
"username": "newuser",
|
|||
|
|
"email": "newuser@example.com",
|
|||
|
|
"password": "password123",
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
assert response.status_code == 201
|
|||
|
|
data = response.json()
|
|||
|
|
assert data["username"] == "newuser"
|
|||
|
|
assert data["email"] == "newuser@example.com"
|
|||
|
|
assert data["is_active"] is True
|
|||
|
|
assert "id" in data
|
|||
|
|
assert "created_at" in data
|
|||
|
|
|
|||
|
|
def test_create_user_duplicate_username(self):
|
|||
|
|
"""测试创建重复用户名应返回 409。"""
|
|||
|
|
response = client.post(
|
|||
|
|
"/api/users",
|
|||
|
|
json={
|
|||
|
|
"username": "alice",
|
|||
|
|
"email": "alice2@example.com",
|
|||
|
|
"password": "password123",
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
assert response.status_code == 409
|
|||
|
|
assert "已被占用" in response.json()["detail"]
|
|||
|
|
|
|||
|
|
def test_create_user_duplicate_email(self):
|
|||
|
|
"""测试创建重复邮箱应返回 409。"""
|
|||
|
|
response = client.post(
|
|||
|
|
"/api/users",
|
|||
|
|
json={
|
|||
|
|
"username": "alice2",
|
|||
|
|
"email": "alice@example.com",
|
|||
|
|
"password": "password123",
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
assert response.status_code == 409
|
|||
|
|
assert "已被注册" in response.json()["detail"]
|
|||
|
|
|
|||
|
|
def test_create_user_validation_error(self):
|
|||
|
|
"""测试用户名太短应返回 422。"""
|
|||
|
|
response = client.post(
|
|||
|
|
"/api/users",
|
|||
|
|
json={
|
|||
|
|
"username": "ab",
|
|||
|
|
"email": "test@example.com",
|
|||
|
|
"password": "password123",
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
assert response.status_code == 422
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ──────────────────────────────────────────────
|
|||
|
|
# 用户查询测试
|
|||
|
|
# ──────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TestGetUsers:
|
|||
|
|
"""用户查询接口测试类。"""
|
|||
|
|
|
|||
|
|
def test_get_all_users(self):
|
|||
|
|
"""测试获取所有用户(分页)。"""
|
|||
|
|
response = client.get("/api/users?skip=0&limit=10")
|
|||
|
|
assert response.status_code == 200
|
|||
|
|
data = response.json()
|
|||
|
|
assert len(data) >= 3
|
|||
|
|
# 验证返回的字段不包含密码
|
|||
|
|
for user in data:
|
|||
|
|
assert "password" not in user
|
|||
|
|
assert "hashed_password" not in user
|
|||
|
|
|
|||
|
|
def test_get_user_by_id_success(self):
|
|||
|
|
"""测试通过 ID 获取用户成功。"""
|
|||
|
|
response = client.get("/api/users/user-001")
|
|||
|
|
assert response.status_code == 200
|
|||
|
|
data = response.json()
|
|||
|
|
assert data["username"] == "alice"
|
|||
|
|
assert data["email"] == "alice@example.com"
|
|||
|
|
|
|||
|
|
def test_get_user_by_id_not_found(self):
|
|||
|
|
"""测试获取不存在的用户返回 404。"""
|
|||
|
|
response = client.get("/api/users/user-999")
|
|||
|
|
assert response.status_code == 404
|
|||
|
|
|
|||
|
|
def test_get_users_pagination(self):
|
|||
|
|
"""测试分页参数生效。"""
|
|||
|
|
response = client.get("/api/users?skip=0&limit=1")
|
|||
|
|
assert response.status_code == 200
|
|||
|
|
data = response.json()
|
|||
|
|
assert len(data) == 1
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ──────────────────────────────────────────────
|
|||
|
|
# 用户更新测试
|
|||
|
|
# ──────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TestUpdateUser:
|
|||
|
|
"""用户更新接口测试类。"""
|
|||
|
|
|
|||
|
|
def test_update_username_success(self):
|
|||
|
|
"""测试成功更新用户名。"""
|
|||
|
|
response = client.put(
|
|||
|
|
"/api/users/user-001",
|
|||
|
|
json={"username": "alice_updated"},
|
|||
|
|
)
|
|||
|
|
assert response.status_code == 200
|
|||
|
|
data = response.json()
|
|||
|
|
assert data["username"] == "alice_updated"
|
|||
|
|
|
|||
|
|
def test_update_user_not_found(self):
|
|||
|
|
"""测试更新不存在的用户返回 404。"""
|
|||
|
|
response = client.put(
|
|||
|
|
"/api/users/user-999",
|
|||
|
|
json={"username": "new_name"},
|
|||
|
|
)
|
|||
|
|
assert response.status_code == 404
|
|||
|
|
|
|||
|
|
def test_update_username_conflict(self):
|
|||
|
|
"""测试更新为已存在的用户名返回 409。"""
|
|||
|
|
response = client.put(
|
|||
|
|
"/api/users/user-001",
|
|||
|
|
json={"username": "bob"},
|
|||
|
|
)
|
|||
|
|
assert response.status_code == 409
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ──────────────────────────────────────────────
|
|||
|
|
# 用户删除测试
|
|||
|
|
# ──────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TestDeleteUser:
|
|||
|
|
"""用户删除接口测试类。"""
|
|||
|
|
|
|||
|
|
def test_delete_user_success(self):
|
|||
|
|
"""测试成功删除用户。"""
|
|||
|
|
response = client.delete("/api/users/user-001")
|
|||
|
|
assert response.status_code == 200
|
|||
|
|
data = response.json()
|
|||
|
|
assert "成功删除" in data["message"]
|
|||
|
|
|
|||
|
|
# 验证用户确实被删除
|
|||
|
|
get_response = client.get("/api/users/user-001")
|
|||
|
|
assert get_response.status_code == 404
|
|||
|
|
|
|||
|
|
def test_delete_user_not_found(self):
|
|||
|
|
"""测试删除不存在的用户返回 404。"""
|
|||
|
|
response = client.delete("/api/users/user-999")
|
|||
|
|
assert response.status_code == 404
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ──────────────────────────────────────────────
|
|||
|
|
# 用户登录测试
|
|||
|
|
# ──────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TestLogin:
|
|||
|
|
"""用户登录接口测试类。"""
|
|||
|
|
|
|||
|
|
def test_login_success(self):
|
|||
|
|
"""测试使用正确的用户名和密码登录成功。"""
|
|||
|
|
response = client.post(
|
|||
|
|
"/api/users/login",
|
|||
|
|
json={"username": "alice", "password": "abc123"},
|
|||
|
|
)
|
|||
|
|
assert response.status_code == 200
|
|||
|
|
data = response.json()
|
|||
|
|
assert data["username"] == "alice"
|
|||
|
|
|
|||
|
|
def test_login_wrong_password(self):
|
|||
|
|
"""测试使用错误密码登录返回 401。"""
|
|||
|
|
response = client.post(
|
|||
|
|
"/api/users/login",
|
|||
|
|
json={"username": "alice", "password": "wrong_password"},
|
|||
|
|
)
|
|||
|
|
assert response.status_code == 401
|
|||
|
|
|
|||
|
|
def test_login_user_not_found(self):
|
|||
|
|
"""测试使用不存在的用户名登录返回 401。"""
|
|||
|
|
response = client.post(
|
|||
|
|
"/api/users/login",
|
|||
|
|
json={"username": "nonexistent", "password": "password123"},
|
|||
|
|
)
|
|||
|
|
assert response.status_code == 401
|