2026-10-12 21:26:08 -04:00
|
|
|
|
"""岗位角色管理接口集成测试(需要 MySQL 与 Redis)。
|
|
|
|
|
|
|
|
|
|
|
|
守的是岗位管理的保护规则与模板绑定 —— 内置岗位被删停会让参数维度值
|
|
|
|
|
|
失效、核算模板断锚;模板编码不校验会让核算引擎取不到公式。
|
|
|
|
|
|
|
|
|
|
|
|
运行前置:``docker compose -f docker/docker-compose.dev.yml up -d``
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import uuid
|
|
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
|
|
|
|
|
|
from app.core.constants import ROLE_USER
|
|
|
|
|
|
|
|
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
|
|
|
|
|
|
|
|
_ROLES = "/api/v1/system/business-roles"
|
|
|
|
|
|
_TEMPLATES = "/api/v1/system/business-roles/meta/templates"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _new_code() -> str:
|
|
|
|
|
|
return f"t_br_{uuid.uuid4().hex[:6]}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestCrudFlow:
|
|
|
|
|
|
"""正向链路:建 → 查(列表 + 模板下拉)→ 改 → 删。"""
|
|
|
|
|
|
|
|
|
|
|
|
def test_新增自定义岗位并联动列表(self, client: TestClient, admin: dict) -> None:
|
|
|
|
|
|
code = _new_code()
|
|
|
|
|
|
resp = client.post(
|
|
|
|
|
|
_ROLES,
|
|
|
|
|
|
json={
|
|
|
|
|
|
"role_code": code,
|
|
|
|
|
|
"role_name": "集成测试岗位",
|
|
|
|
|
|
"calc_template": "TPL_SALESPERSON_DECAY",
|
|
|
|
|
|
"description": "集成测试",
|
|
|
|
|
|
"sort_order": 50,
|
|
|
|
|
|
},
|
|
|
|
|
|
headers=admin["headers"],
|
|
|
|
|
|
)
|
|
|
|
|
|
assert resp.status_code == 200, resp.text
|
|
|
|
|
|
body = resp.json()["data"]
|
|
|
|
|
|
assert body["role_code"] == code
|
|
|
|
|
|
assert body["is_builtin"] == 0
|
|
|
|
|
|
assert body["status"] == 1
|
|
|
|
|
|
|
|
|
|
|
|
listed = client.get(_ROLES, params={"keyword": code}, headers=admin["headers"])
|
|
|
|
|
|
assert any(r["role_code"] == code for r in listed.json()["data"]["list"])
|
|
|
|
|
|
|
|
|
|
|
|
# 清理
|
|
|
|
|
|
assert client.delete(f"{_ROLES}/{body['id']}", headers=admin["headers"]).status_code == 200
|
|
|
|
|
|
|
|
|
|
|
|
def test_重复编码拒绝(self, client: TestClient, admin: dict) -> None:
|
|
|
|
|
|
code = _new_code()
|
|
|
|
|
|
payload = {
|
|
|
|
|
|
"role_code": code,
|
|
|
|
|
|
"role_name": "首个",
|
|
|
|
|
|
"calc_template": "TPL_SALESPERSON_DECAY",
|
|
|
|
|
|
}
|
|
|
|
|
|
assert client.post(_ROLES, json=payload, headers=admin["headers"]).status_code == 200
|
|
|
|
|
|
dup = client.post(_ROLES, json=payload, headers=admin["headers"])
|
|
|
|
|
|
assert dup.status_code == 400
|
|
|
|
|
|
assert "已存在" in dup.json()["message"]
|
|
|
|
|
|
client.delete(
|
|
|
|
|
|
f"{_ROLES}/{client.get(_ROLES, params={'keyword': code}, headers=admin['headers']).json()['data']['list'][0]['id']}",
|
|
|
|
|
|
headers=admin["headers"],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_模板编码非法拒绝(self, client: TestClient, admin: dict) -> None:
|
|
|
|
|
|
resp = client.post(
|
|
|
|
|
|
_ROLES,
|
|
|
|
|
|
json={
|
|
|
|
|
|
"role_code": _new_code(),
|
|
|
|
|
|
"role_name": "坏模板岗位",
|
|
|
|
|
|
"calc_template": "TPL_NOT_EXIST",
|
|
|
|
|
|
},
|
|
|
|
|
|
headers=admin["headers"],
|
|
|
|
|
|
)
|
|
|
|
|
|
assert resp.status_code == 400
|
|
|
|
|
|
|
|
|
|
|
|
def test_编辑岗位换模板生效(self, client: TestClient, admin: dict) -> None:
|
|
|
|
|
|
code = _new_code()
|
|
|
|
|
|
created = client.post(
|
|
|
|
|
|
_ROLES,
|
|
|
|
|
|
json={
|
|
|
|
|
|
"role_code": code,
|
|
|
|
|
|
"role_name": "待改岗位",
|
|
|
|
|
|
"calc_template": "TPL_SALESPERSON_DECAY",
|
|
|
|
|
|
},
|
|
|
|
|
|
headers=admin["headers"],
|
|
|
|
|
|
).json()["data"]
|
|
|
|
|
|
updated = client.put(
|
|
|
|
|
|
f"{_ROLES}/{created['id']}",
|
|
|
|
|
|
json={
|
|
|
|
|
|
"role_name": "已改岗位",
|
|
|
|
|
|
"calc_template": "TPL_ENTERPRISE_FIXED",
|
|
|
|
|
|
"sort_order": 7,
|
|
|
|
|
|
},
|
|
|
|
|
|
headers=admin["headers"],
|
|
|
|
|
|
)
|
|
|
|
|
|
assert updated.status_code == 200
|
|
|
|
|
|
body = updated.json()["data"]
|
|
|
|
|
|
assert body["role_name"] == "已改岗位"
|
|
|
|
|
|
assert body["calc_template"] == "TPL_ENTERPRISE_FIXED"
|
|
|
|
|
|
assert body["role_code"] == code # 编码不变
|
|
|
|
|
|
client.delete(f"{_ROLES}/{created['id']}", headers=admin["headers"])
|
|
|
|
|
|
|
|
|
|
|
|
def test_模板下拉含五个模板且声明参数已注册(self, client: TestClient, admin: dict) -> None:
|
|
|
|
|
|
options = client.get(_TEMPLATES, headers=admin["headers"]).json()["data"]
|
|
|
|
|
|
assert len(options) == 5
|
|
|
|
|
|
for tpl in options:
|
|
|
|
|
|
assert tpl["params"], f"{tpl['code']} 未声明参数"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestBuiltinProtection:
|
|
|
|
|
|
"""内置岗位(种子 3 个)禁删禁停 —— 参数维度与模板按编码锚定。"""
|
|
|
|
|
|
|
|
|
|
|
|
def test_停用内置岗位_403(self, client: TestClient, admin: dict) -> None:
|
|
|
|
|
|
rows = client.get(
|
|
|
|
|
|
_ROLES, params={"keyword": "salesperson"}, headers=admin["headers"]
|
|
|
|
|
|
).json()["data"]["list"]
|
|
|
|
|
|
target = next(r for r in rows if r["role_code"] == "salesperson" and r["is_builtin"] == 1)
|
|
|
|
|
|
resp = client.put(
|
|
|
|
|
|
f"{_ROLES}/{target['id']}/status", json={"status": 0}, headers=admin["headers"]
|
|
|
|
|
|
)
|
|
|
|
|
|
assert resp.status_code == 403
|
|
|
|
|
|
|
|
|
|
|
|
def test_删除内置岗位_403(self, client: TestClient, admin: dict) -> None:
|
|
|
|
|
|
rows = client.get(
|
|
|
|
|
|
_ROLES, params={"keyword": "sales_manager"}, headers=admin["headers"]
|
|
|
|
|
|
).json()["data"]["list"]
|
|
|
|
|
|
target = next(r for r in rows if r["role_code"] == "sales_manager")
|
|
|
|
|
|
resp = client.delete(f"{_ROLES}/{target['id']}", headers=admin["headers"])
|
|
|
|
|
|
assert resp.status_code == 403
|
|
|
|
|
|
|
|
|
|
|
|
def test_种子内置岗位三个且不可删标记正确(self, client: TestClient, admin: dict) -> None:
|
|
|
|
|
|
rows = client.get(_ROLES, params={"pageSize": 50}, headers=admin["headers"]).json()["data"][
|
|
|
|
|
|
"list"
|
|
|
|
|
|
]
|
|
|
|
|
|
builtins = [r for r in rows if r["is_builtin"] == 1]
|
|
|
|
|
|
assert {r["role_code"] for r in builtins} == {
|
|
|
|
|
|
"salesperson",
|
|
|
|
|
|
"sales_manager",
|
|
|
|
|
|
"info_officer",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestPermission:
|
2026-10-19 21:44:43 -04:00
|
|
|
|
"""权限:管理接口普通用户 403;只读选项登录即可(报表筛选依赖)。"""
|
2026-10-12 21:26:08 -04:00
|
|
|
|
|
|
|
|
|
|
def test_普通用户访问岗位列表_403(self, client: TestClient, make_user, auth_headers) -> None:
|
|
|
|
|
|
user = make_user([ROLE_USER])
|
|
|
|
|
|
headers = auth_headers(user)
|
|
|
|
|
|
assert client.get(_ROLES, headers=headers).status_code == 403
|
2026-10-19 21:44:43 -04:00
|
|
|
|
|
|
|
|
|
|
def test_普通用户可读取岗位选项(self, client: TestClient, make_user, auth_headers) -> None:
|
|
|
|
|
|
"""财务/领导做报表岗位筛选时不能因管理员接口 403 而失去筛选能力。"""
|
|
|
|
|
|
user = make_user([ROLE_USER])
|
|
|
|
|
|
headers = auth_headers(user)
|
|
|
|
|
|
resp = client.get("/api/v1/base/business-roles/options", headers=headers)
|
|
|
|
|
|
assert resp.status_code == 200, resp.text
|
|
|
|
|
|
codes = {row["role_code"] for row in resp.json()["data"]}
|
|
|
|
|
|
assert {"salesperson", "sales_manager", "info_officer"} <= codes
|