"""Admin RBAC 角色/权限测试:可见页下发、角色 CRUD 仅 super_admin、内建/在用保护。""" from __future__ import annotations import pytest from fastapi.testclient import TestClient from app.admin.main import admin_app from app.admin.repositories import admin_user as admin_repo from app.core.security import hash_password from app.db.session import SessionLocal @pytest.fixture() def admin_client() -> TestClient: return TestClient(admin_app) def _token(username: str, role: str) -> str: db = SessionLocal() try: a = admin_repo.get_by_username(db, username) if a is None: admin_repo.create_admin(db, username=username, password="pass1234", role=role) else: a.password_hash = hash_password("pass1234") a.role = role a.status = "active" db.commit() finally: db.close() c = TestClient(admin_app) return c.post( "/admin/api/auth/login", json={"username": username, "password": "pass1234"} ).json()["access_token"] @pytest.fixture() def super_token() -> str: return _token("r_super", "super_admin") @pytest.fixture() def operator_token() -> str: return _token("r_operator", "operator") def _auth(t: str) -> dict: return {"Authorization": f"Bearer {t}"} def test_super_pages_all_operator_limited(admin_client, super_token, operator_token) -> None: su = admin_client.get("/admin/api/auth/me", headers=_auth(super_token)).json() assert "admins" in su["pages"] and "dashboard" in su["pages"] # 超管全页 op = admin_client.get("/admin/api/auth/me", headers=_auth(operator_token)).json() assert "dashboard" in op["pages"] and "admins" not in op["pages"] # 运营看不到管理员页 def test_roles_endpoints_super_only(admin_client, super_token, operator_token) -> None: assert admin_client.get("/admin/api/roles", headers=_auth(super_token)).status_code == 200 assert admin_client.get("/admin/api/roles", headers=_auth(operator_token)).status_code == 403 def test_role_crud(admin_client, super_token) -> None: cat = admin_client.get("/admin/api/roles/catalog", headers=_auth(super_token)).json() assert any(g["group"] == "看板" for g in cat) r = admin_client.post( "/admin/api/roles", json={"name": "审核", "pages": ["dashboard", "feedbacks", "xx-bad"]}, headers=_auth(super_token), ) assert r.status_code == 200, r.text rid = r.json()["id"] assert set(r.json()["pages"]) == {"dashboard", "feedbacks"} # 非法 key 被过滤 # 重名 409 assert admin_client.post( "/admin/api/roles", json={"name": "审核", "pages": []}, headers=_auth(super_token) ).status_code == 409 # 改可见页 u = admin_client.patch( f"/admin/api/roles/{rid}", json={"pages": ["dashboard"]}, headers=_auth(super_token) ) assert u.status_code == 200 and u.json()["pages"] == ["dashboard"] # 删 assert admin_client.delete(f"/admin/api/roles/{rid}", headers=_auth(super_token)).status_code == 200 def test_builtin_super_admin_protected(admin_client, super_token) -> None: roles = admin_client.get("/admin/api/roles", headers=_auth(super_token)).json() sa = next(r for r in roles if r["name"] == "super_admin") assert sa["is_builtin"] and len(sa["pages"]) >= 10 # 全部页 assert admin_client.patch( f"/admin/api/roles/{sa['id']}", json={"pages": []}, headers=_auth(super_token) ).status_code == 400 assert admin_client.delete( f"/admin/api/roles/{sa['id']}", headers=_auth(super_token) ).status_code == 400 def test_delete_role_in_use_blocked(admin_client, super_token) -> None: admin_client.post( "/admin/api/admins", json={"username": "role_holder", "password": "pass1234", "role": "operator"}, headers=_auth(super_token), ) roles = admin_client.get("/admin/api/roles", headers=_auth(super_token)).json() op = next(r for r in roles if r["name"] == "operator") assert op["in_use"] >= 1 assert admin_client.delete( f"/admin/api/roles/{op['id']}", headers=_auth(super_token) ).status_code == 400 def test_builtin_roles_labels_and_pages(admin_client, super_token) -> None: roles = {r["name"]: r for r in admin_client.get("/admin/api/roles", headers=_auth(super_token)).json()} # 中文展示名(key 不变) assert roles["super_admin"]["label"] == "管理员" assert roles["operator"]["label"] == "运营" assert roles["finance"]["label"] == "财务" assert roles["tech"]["label"] == "技术" # 页集对齐 Prototypes/dashboard/permissions.md 的 ROLES assert set(roles["finance"]["pages"]) == {"dashboard", "ad-revenue-report", "cps", "withdraws"} assert set(roles["tech"]["pages"]) == { "dashboard", "device-liveness", "config", "ad-revenue", "event-logs", "audit-logs", } def test_ui_created_admin_shows_password_script_admin_hidden(admin_client, super_token) -> None: # UI 建号:账号列表回显明文登录密码(供权限管理页编辑复看) admin_client.post( "/admin/api/admins", json={"username": "pw_show_user", "password": "pass1234", "role": "operator"}, headers=_auth(super_token), ) admins = {a["username"]: a for a in admin_client.get("/admin/api/admins", headers=_auth(super_token)).json()} assert admins["pw_show_user"]["password"] == "pass1234" # 经 repo/脚本直建的账号(r_super,无留存明文)→ password 为 None,前端不显示 assert admins["r_super"]["password"] is None # 登录 / me 不下发明文(保持 None) me = admin_client.get("/admin/api/auth/me", headers=_auth(super_token)).json() assert me.get("password") is None def test_create_admin_rejects_unknown_role(admin_client, super_token) -> None: r = admin_client.post( "/admin/api/admins", json={"username": "bad_role_user", "password": "pass1234", "role": "不存在的角色"}, headers=_auth(super_token), ) assert r.status_code == 400