259 lines
9.2 KiB
Python
259 lines
9.2 KiB
Python
"""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
|