所有需求实现完成
This commit is contained in:
@@ -37,6 +37,10 @@ TEST_BASE_URL=http://localhost:8000 pytest tests/ -v
|
||||
| 接口 | 用例 |
|
||||
|------|------|
|
||||
| POST /api/auth/login | 成功返回 token/user、错误凭证 401、缺 username/password 返回 400 |
|
||||
| POST /api/users/create | 无 token 401、非管理员 403、管理员 201+id+message、缺 username/password/role 400 |
|
||||
| POST /api/users/update | 无 token 401、非管理员 403、管理员 200+id+message、不存在 404 |
|
||||
| POST /api/users/delete | 无 token 401、非管理员 403、管理员 200+message、不存在 404、不能删自己 400 |
|
||||
| POST /api/projects/statistics | 无 token 401;有 token 默认/全部 返回 total、statisticsType、byType;具体类型返回 total、statisticsType、list |
|
||||
| POST /api/projects/list | 无 token 401;有 token 返回 list/total/page/pageSize;无数据 list 空且 total=0;支持 searchType+keyword(name/code)、progress、cost、dateFilterType+dateFrom/dateTo、dateAbnormal;列表项含 id/projectName/contractCode/progress/cost/updatedAt;page/pageSize 回显 |
|
||||
| POST /api/projects/detail | 无 token 401、缺 id 400/422、存在项目返回完整五类字段、不存在 404 |
|
||||
| POST /api/projects/create | 无 token 401、非市场部 403、市场部且必填齐全 201、缺合同编号/项目名称 400、校验失败可含 errors 数组 |
|
||||
|
||||
@@ -10,6 +10,64 @@ def _auth_header(token: str) -> dict:
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
# ---------- POST /api/projects/statistics(API 文档第 3 节)----------
|
||||
|
||||
|
||||
def test_statistics_without_token_returns_401(client):
|
||||
"""项目统计无 token 时返回 401"""
|
||||
r = client.post("/api/projects/statistics", json={})
|
||||
assert r.status_code == 401
|
||||
|
||||
|
||||
def test_statistics_with_token_default_returns_200_with_total_and_by_type(client, auth_token):
|
||||
"""有 token 且未传 statisticsType(或传全部)时返回 200,含 total、statisticsType、byType"""
|
||||
r = client.post(
|
||||
"/api/projects/statistics",
|
||||
headers=_auth_header(auth_token),
|
||||
json={},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert "total" in body
|
||||
assert isinstance(body["total"], int)
|
||||
assert "statisticsType" in body
|
||||
assert body["statisticsType"] == "全部"
|
||||
assert "byType" in body
|
||||
assert isinstance(body["byType"], dict)
|
||||
|
||||
|
||||
def test_statistics_with_statistics_type_all_returns_by_type(client, auth_token):
|
||||
"""statisticsType=全部 时返回 byType 各类型数量"""
|
||||
r = client.post(
|
||||
"/api/projects/statistics",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"statisticsType": "全部"},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body.get("statisticsType") == "全部"
|
||||
assert "byType" in body
|
||||
for _, count in body["byType"].items():
|
||||
assert isinstance(count, (int, float))
|
||||
|
||||
|
||||
def test_statistics_with_specific_type_returns_200_with_total_and_list(client, auth_token):
|
||||
"""statisticsType 为具体类型时返回 200,含 total、statisticsType、list"""
|
||||
r = client.post(
|
||||
"/api/projects/statistics",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"statisticsType": "基建工程"},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert "total" in body
|
||||
assert isinstance(body["total"], int)
|
||||
assert "statisticsType" in body
|
||||
assert body["statisticsType"] == "基建工程"
|
||||
assert "list" in body
|
||||
assert isinstance(body["list"], list)
|
||||
|
||||
|
||||
# ---------- POST /api/projects/list ----------
|
||||
|
||||
|
||||
@@ -495,7 +553,7 @@ def test_e2e_login_then_get_project_list(client):
|
||||
|
||||
|
||||
def test_e2e_login_then_create_project_then_list_includes_it(client):
|
||||
"""E2E:登录(市场部)→ 创建项目 → 获取列表,列表中包含新建项目"""
|
||||
"""E2E:登录(市场部)→ 创建项目 → 按名称搜索列表,列表中包含新建项目"""
|
||||
token = _login_and_get_token(client, "market", "123456")
|
||||
project_name = "E2E流程新建项目"
|
||||
contract_code = "HT-E2E-001"
|
||||
@@ -509,14 +567,14 @@ def test_e2e_login_then_create_project_then_list_includes_it(client):
|
||||
list_r = client.post(
|
||||
"/api/projects/list",
|
||||
headers=_auth_header(token),
|
||||
json={"page": 1, "pageSize": 100},
|
||||
json={"page": 1, "pageSize": 20, "searchType": "name", "keyword": project_name},
|
||||
)
|
||||
assert list_r.status_code == 200
|
||||
items = list_r.json().get("list", [])
|
||||
ids = [p["id"] for p in items]
|
||||
assert pid in ids
|
||||
names = [p.get("projectName") for p in items if p.get("id") == pid]
|
||||
assert project_name in names or len(names) >= 1
|
||||
assert pid in ids, f"新建项目 id {pid} 应在搜索结果中,list ids: {ids[:5]}..."
|
||||
found = next((p for p in items if p.get("id") == pid), None)
|
||||
assert found is not None and found.get("projectName") == project_name
|
||||
|
||||
|
||||
def test_e2e_login_then_create_then_detail_then_update_then_detail_reflects(client):
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
"""
|
||||
用户管理 API 测试(依据 API 文档 2.2 节)
|
||||
|
||||
仅管理员可创建、修改、删除用户。测试对已启动服务发起 HTTP 请求,使用 admin token。
|
||||
"""
|
||||
import uuid
|
||||
|
||||
|
||||
def _auth_header(token: str) -> dict:
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
# ---------- POST /api/users/create ----------
|
||||
|
||||
|
||||
def test_users_create_without_token_returns_401(client):
|
||||
"""创建用户无 token 时返回 401"""
|
||||
r = client.post(
|
||||
"/api/users/create",
|
||||
json={"username": "u1", "password": "123456", "role": "工程部"},
|
||||
)
|
||||
assert r.status_code == 401
|
||||
|
||||
|
||||
def test_users_create_non_admin_returns_403(client, engineer_token):
|
||||
"""非管理员创建用户返回 403"""
|
||||
r = client.post(
|
||||
"/api/users/create",
|
||||
headers=_auth_header(engineer_token),
|
||||
json={"username": "u_engineer_cannot", "password": "123456", "role": "工程部"},
|
||||
)
|
||||
assert r.status_code == 403
|
||||
assert r.json().get("code") == 403
|
||||
|
||||
|
||||
def test_users_create_admin_returns_201_with_id_and_message(client, auth_token):
|
||||
"""管理员创建用户返回 201 且包含 id 和 message「创建成功」"""
|
||||
username = f"testuser_create_{uuid.uuid4().hex[:8]}"
|
||||
r = client.post(
|
||||
"/api/users/create",
|
||||
headers=_auth_header(auth_token),
|
||||
json={
|
||||
"username": username,
|
||||
"password": "123456",
|
||||
"role": "技经部",
|
||||
"displayName": "测试创建用户",
|
||||
},
|
||||
)
|
||||
assert r.status_code == 201
|
||||
body = r.json()
|
||||
assert "id" in body
|
||||
assert body.get("message") == "创建成功"
|
||||
|
||||
|
||||
def test_users_create_missing_username_returns_400(client, auth_token):
|
||||
"""创建用户缺少 username 时返回 400"""
|
||||
r = client.post(
|
||||
"/api/users/create",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"password": "123456", "role": "工程部"},
|
||||
)
|
||||
assert r.status_code == 400
|
||||
assert r.json().get("code") == 400
|
||||
|
||||
|
||||
def test_users_create_missing_password_returns_400(client, auth_token):
|
||||
"""创建用户缺少 password 时返回 400"""
|
||||
r = client.post(
|
||||
"/api/users/create",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"username": "testuser_nopwd", "role": "工程部"},
|
||||
)
|
||||
assert r.status_code == 400
|
||||
assert r.json().get("code") == 400
|
||||
|
||||
|
||||
def test_users_create_missing_role_returns_400(client, auth_token):
|
||||
"""创建用户缺少 role 时返回 400"""
|
||||
r = client.post(
|
||||
"/api/users/create",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"username": "testuser_norole", "password": "123456"},
|
||||
)
|
||||
assert r.status_code == 400
|
||||
assert r.json().get("code") == 400
|
||||
|
||||
|
||||
# ---------- POST /api/users/update ----------
|
||||
|
||||
|
||||
def test_users_update_without_token_returns_401(client):
|
||||
"""更新用户无 token 时返回 401"""
|
||||
r = client.post("/api/users/update", json={"id": "any-id"})
|
||||
assert r.status_code == 401
|
||||
|
||||
|
||||
def test_users_update_non_admin_returns_403(client, engineer_token, auth_token):
|
||||
"""非管理员更新用户返回 403"""
|
||||
username = f"testuser_for_update_{uuid.uuid4().hex[:8]}"
|
||||
create_r = client.post(
|
||||
"/api/users/create",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"username": username, "password": "123456", "role": "财务部"},
|
||||
)
|
||||
assert create_r.status_code == 201
|
||||
uid = create_r.json()["id"]
|
||||
r = client.post(
|
||||
"/api/users/update",
|
||||
headers=_auth_header(engineer_token),
|
||||
json={"id": uid, "displayName": "不应成功"},
|
||||
)
|
||||
assert r.status_code == 403
|
||||
assert r.json().get("code") == 403
|
||||
|
||||
|
||||
def test_users_update_admin_returns_200_with_id_and_message(client, auth_token):
|
||||
"""管理员更新用户返回 200 且包含 id 和 message「保存成功」"""
|
||||
username = f"testuser_update_{uuid.uuid4().hex[:8]}"
|
||||
create_r = client.post(
|
||||
"/api/users/create",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"username": username, "password": "123456", "role": "物贸部"},
|
||||
)
|
||||
assert create_r.status_code == 201
|
||||
uid = create_r.json()["id"]
|
||||
r = client.post(
|
||||
"/api/users/update",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"id": uid, "displayName": "更新后的显示名", "role": "财务部"},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body.get("id") == uid
|
||||
assert body.get("message") == "保存成功"
|
||||
|
||||
|
||||
def test_users_update_nonexistent_returns_404(client, auth_token):
|
||||
"""更新不存在用户返回 404"""
|
||||
r = client.post(
|
||||
"/api/users/update",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"id": "non-existent-user-id", "displayName": "任意"},
|
||||
)
|
||||
assert r.status_code == 404
|
||||
assert r.json().get("code") == 404
|
||||
|
||||
|
||||
# ---------- POST /api/users/delete ----------
|
||||
|
||||
|
||||
def test_users_delete_without_token_returns_401(client):
|
||||
"""删除用户无 token 时返回 401"""
|
||||
r = client.post("/api/users/delete", json={"id": "any-id"})
|
||||
assert r.status_code == 401
|
||||
|
||||
|
||||
def test_users_delete_non_admin_returns_403(client, engineer_token, auth_token):
|
||||
"""非管理员删除用户返回 403"""
|
||||
username = f"testuser_for_del_{uuid.uuid4().hex[:8]}"
|
||||
create_r = client.post(
|
||||
"/api/users/create",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"username": username, "password": "123456", "role": "工程部"},
|
||||
)
|
||||
assert create_r.status_code == 201
|
||||
uid = create_r.json()["id"]
|
||||
r = client.post(
|
||||
"/api/users/delete",
|
||||
headers=_auth_header(engineer_token),
|
||||
json={"id": uid},
|
||||
)
|
||||
assert r.status_code == 403
|
||||
assert r.json().get("code") == 403
|
||||
|
||||
|
||||
def test_users_delete_admin_returns_200_with_message(client, auth_token):
|
||||
"""管理员删除用户返回 200 且 message「删除成功」"""
|
||||
username = f"testuser_delete_{uuid.uuid4().hex[:8]}"
|
||||
create_r = client.post(
|
||||
"/api/users/create",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"username": username, "password": "123456", "role": "工程部"},
|
||||
)
|
||||
assert create_r.status_code == 201
|
||||
uid = create_r.json()["id"]
|
||||
r = client.post(
|
||||
"/api/users/delete",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"id": uid},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
assert r.json().get("message") == "删除成功"
|
||||
|
||||
|
||||
def test_users_delete_nonexistent_returns_404(client, auth_token):
|
||||
"""删除不存在用户返回 404"""
|
||||
r = client.post(
|
||||
"/api/users/delete",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"id": "non-existent-user-id"},
|
||||
)
|
||||
assert r.status_code == 404
|
||||
assert r.json().get("code") == 404
|
||||
|
||||
|
||||
def test_users_delete_self_returns_400(client, auth_token):
|
||||
"""管理员不能删除当前登录用户自己,返回 400"""
|
||||
# 先登录拿到当前用户 id(从 login 响应的 user.id)
|
||||
login_r = client.post("/api/auth/login", json={"username": "admin", "password": "123456"})
|
||||
assert login_r.status_code == 200
|
||||
current_user_id = login_r.json()["user"]["id"]
|
||||
r = client.post(
|
||||
"/api/users/delete",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"id": current_user_id},
|
||||
)
|
||||
assert r.status_code == 400
|
||||
body = r.json()
|
||||
assert body.get("code") == 400
|
||||
assert "message" in body
|
||||
|
||||
|
||||
# ---------- E2E:管理员 创建 → 修改 → 删除 用户 ----------
|
||||
|
||||
|
||||
def test_e2e_admin_create_then_update_then_delete_user(client, auth_token):
|
||||
"""E2E:管理员创建用户 → 修改用户 → 删除用户"""
|
||||
username = f"testuser_e2e_{uuid.uuid4().hex[:8]}"
|
||||
create_r = client.post(
|
||||
"/api/users/create",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"username": username, "password": "123456", "role": "市场部", "displayName": "E2E初始"},
|
||||
)
|
||||
assert create_r.status_code == 201
|
||||
uid = create_r.json()["id"]
|
||||
assert uid
|
||||
|
||||
update_r = client.post(
|
||||
"/api/users/update",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"id": uid, "displayName": "E2E已修改", "role": "技经部"},
|
||||
)
|
||||
assert update_r.status_code == 200
|
||||
assert update_r.json().get("message") == "保存成功"
|
||||
|
||||
delete_r = client.post(
|
||||
"/api/users/delete",
|
||||
headers=_auth_header(auth_token),
|
||||
json={"id": uid},
|
||||
)
|
||||
assert delete_r.status_code == 200
|
||||
assert delete_r.json().get("message") == "删除成功"
|
||||
Reference in New Issue
Block a user