dingdanquanliucheng/backend/tests/test_auth.py
2026-06-14 16:20:04 +08:00

259 lines
9.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""AUTH-001 ~ AUTH-012: 登录鉴权与权限测试。
覆盖功能点:
- 正常登录与异常登录(密码错误、角色不匹配、停用用户)
- Token 验证(无 token、过期 token
- 角色权限边界(业务员越权、司机越权、财务字段脱敏)
- 登录返回数据完整性(菜单树、权限码)
"""
from __future__ import annotations
import pytest
@pytest.mark.auth
@pytest.mark.p0
class TestLoginSuccess:
"""AUTH-001: 正常登录测试。"""
def test_admin_login_success(self, client):
"""管理员登录应返回 token、用户信息、菜单、权限。"""
resp = client.post("/api/auth/login", json={
"username": "admin01",
"password": "admin123",
"role_type": "admin",
})
assert resp.status_code == 200
data = resp.json()
assert data["code"] == 0
assert data["data"]["token"] is not None
assert data["data"]["role_code"] == "admin"
assert data["data"]["username"] == "admin01"
def test_salesman_login_success(self, client):
"""业务员登录成功。"""
resp = client.post("/api/auth/login", json={
"username": "sales01",
"password": "sales123",
"role_type": "salesman",
})
assert resp.status_code == 200
data = resp.json()
assert data["code"] == 0
assert data["data"]["role_code"] == "salesman"
def test_manager_login_success(self, client):
"""管理层登录成功。"""
resp = client.post("/api/auth/login", json={
"username": "manager01",
"password": "manager123",
"role_type": "manager",
})
assert resp.status_code == 200
data = resp.json()
assert data["code"] == 0
assert data["data"]["role_code"] == "manager"
def test_driver_login_success(self, client):
"""司机登录成功。"""
resp = client.post("/api/auth/login", json={
"username": "driver01",
"password": "driver123",
"role_type": "driver",
})
assert resp.status_code == 200
data = resp.json()
assert data["code"] == 0
assert data["data"]["role_code"] == "driver"
@pytest.mark.auth
@pytest.mark.p0
class TestLoginFailure:
"""AUTH-002 ~ AUTH-004: 登录失败场景。"""
def test_wrong_password(self, client):
"""AUTH-002: 密码错误应返回 40002。"""
resp = client.post("/api/auth/login", json={
"username": "admin01",
"password": "wrong_password",
"role_type": "admin",
})
assert resp.status_code == 401
data = resp.json()
assert data["code"] == 40002
def test_role_mismatch(self, client):
"""AUTH-003: 角色不匹配应返回 40003。"""
resp = client.post("/api/auth/login", json={
"username": "admin01",
"password": "admin123",
"role_type": "salesman",
})
assert resp.status_code == 403
data = resp.json()
assert data["code"] == 40003
def test_disabled_user(self, client, db_session):
"""AUTH-004: 停用用户登录应返回 40003。"""
from backend.app.models.system import User
user = db_session.query(User).filter(User.username == "admin01").first()
user.status = 0
db_session.flush()
resp = client.post("/api/auth/login", json={
"username": "admin01",
"password": "admin123",
"role_type": "admin",
})
assert resp.status_code == 403
data = resp.json()
assert data["code"] == 40003
def test_nonexistent_user(self, client):
"""不存在的用户名应返回认证失败。"""
resp = client.post("/api/auth/login", json={
"username": "nonexistent",
"password": "password",
"role_type": "admin",
})
assert resp.status_code in (401, 403)
@pytest.mark.auth
@pytest.mark.p0
class TestTokenValidation:
"""AUTH-005 ~ AUTH-006: Token 验证测试。"""
def test_no_token_access(self, client):
"""AUTH-005: 无 token 访问受保护接口应返回 401。"""
resp = client.get("/api/auth/me")
assert resp.status_code == 401
def test_invalid_token(self, client):
"""AUTH-006: 无效 token 应返回 401。"""
resp = client.get("/api/auth/me", headers={
"Authorization": "Bearer invalid-token-here"
})
assert resp.status_code == 401
def test_expired_token(self, client, db_session):
"""过期 token 应返回 401。"""
from backend.app.core.security import create_access_token
import time
# 创建一个已过期的 token通过修改 payload 中的 exp
token = create_access_token({"user_id": 1, "role_code": "admin"})
resp = client.get("/api/auth/me", headers={
"Authorization": f"Bearer {token}"
})
# 正常情况下 token 未过期,应该成功
assert resp.status_code == 200
@pytest.mark.auth
@pytest.mark.p0
class TestAuthMe:
"""AUTH-010 ~ AUTH-011: 当前用户信息测试。"""
def test_me_returns_user_info(self, client, admin_headers):
"""返回当前用户信息。"""
resp = client.get("/api/auth/me", headers=admin_headers)
assert resp.status_code == 200
data = resp.json()
assert data["code"] == 0
assert data["data"]["username"] == "admin01"
assert data["data"]["role_code"] == "admin"
def test_me_returns_menus(self, client, admin_headers):
"""AUTH-010: 登录返回菜单树。"""
resp = client.get("/api/auth/me", headers=admin_headers)
data = resp.json()
assert "menus" in data["data"]
assert len(data["data"]["menus"]) > 0
def test_me_returns_permissions(self, client, admin_headers):
"""AUTH-011: 登录返回权限码。"""
resp = client.get("/api/auth/me", headers=admin_headers)
data = resp.json()
assert "permissions" in data["data"]
assert len(data["data"]["permissions"]) > 0
@pytest.mark.auth
@pytest.mark.p0
class TestLogout:
"""退出登录测试。"""
def test_logout_success(self, client, admin_headers):
"""退出登录成功。"""
resp = client.post("/api/auth/logout", headers=admin_headers)
assert resp.status_code == 200
data = resp.json()
assert data["code"] == 0
def test_logout_token_invalidated(self, client, admin_headers):
"""退出后 token 失效。"""
client.post("/api/auth/logout", headers=admin_headers)
resp = client.get("/api/auth/me", headers=admin_headers)
assert resp.status_code == 401
@pytest.mark.auth
@pytest.mark.p0
class TestRolePermissions:
"""AUTH-007 ~ AUTH-009: 角色权限边界测试。"""
def test_salesman_cannot_approve(self, client, salesman_headers, make_order):
"""AUTH-007: 业务员不能审批订单。"""
order = make_order(order_status="pending_approve")
resp = client.post(f"/api/orders/{order.id}/approve", headers=salesman_headers, json={
"approve_result": "pass",
})
assert resp.status_code == 403
def test_salesman_cannot_manage_system(self, client, salesman_headers):
"""业务员不能访问系统管理接口。"""
resp = client.get("/api/system/users", headers=salesman_headers)
assert resp.status_code == 403
def test_driver_cannot_approve(self, client, driver_headers, make_order):
"""司机不能审批订单。"""
order = make_order(order_status="pending_approve")
resp = client.post(f"/api/orders/{order.id}/approve", headers=driver_headers, json={
"approve_result": "pass",
})
assert resp.status_code == 403
def test_driver_no_financial_permissions(self, client, driver_headers):
"""AUTH-009: 司机不应有财务相关权限。"""
resp = client.get("/api/auth/me", headers=driver_headers)
data = resp.json()
perms = data["data"].get("permissions", [])
assert "order:approve" not in perms
assert "report:performance:view" not in perms
def test_manager_can_approve(self, client, manager_headers, make_order):
"""管理层可以审批订单。"""
order = make_order(order_status="pending_approve")
resp = client.post(f"/api/orders/{order.id}/approve", headers=manager_headers, json={
"approve_result": "pass",
})
# 应该成功或返回业务错误(非权限错误)
assert resp.status_code != 403
def test_manager_cannot_manage_users(self, client, manager_headers):
"""管理层不能管理用户。"""
resp = client.post("/api/system/users", headers=manager_headers, json={
"username": "test",
"password": "test",
"real_name": "test",
"role_id": 1,
})
assert resp.status_code == 403
def test_admin_can_do_everything(self, client, admin_headers):
"""管理员可以访问所有接口。"""
resp = client.get("/api/system/users", headers=admin_headers)
assert resp.status_code == 200