You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
6.0 KiB
Python
188 lines
6.0 KiB
Python
from fastapi.testclient import TestClient
|
|
|
|
from iti import create_app
|
|
from iti.auth import create_access_token
|
|
from iti.config import BaseConfig
|
|
from iti.db import Base
|
|
from iti_system import create_system_module
|
|
from iti_system.models import Role, SysMenu, User
|
|
from iti_system.seeds import seed_system_data
|
|
|
|
|
|
def make_app():
|
|
app = create_app(
|
|
modules=[create_system_module()],
|
|
config_mapping=BaseConfig(
|
|
database_url="sqlite+pysqlite:///:memory:",
|
|
testing=True,
|
|
jwt_secret_key="test-secret",
|
|
ratelimit_enabled=False,
|
|
service_tokens={"erp": "svc-token"},
|
|
audit_enabled=True,
|
|
audit_flush_interval_seconds=999,
|
|
),
|
|
)
|
|
Base.metadata.create_all(app.state.db_engine)
|
|
with app.state.db_sessionmaker() as db:
|
|
seed_system_data(db, app.state.iti_modules)
|
|
return app
|
|
|
|
|
|
def login(client: TestClient) -> dict:
|
|
response = client.post(
|
|
"/auth/loginByPassword",
|
|
json={"username": "admin", "password": "123456"},
|
|
)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["code"] == 200
|
|
return {"Authorization": f"Bearer {data['data']['accessToken']}"}
|
|
|
|
|
|
def test_system_module_registers_routes_and_permissions():
|
|
app = make_app()
|
|
paths = {route.path for route in app.routes}
|
|
registry = app.state.iti_modules
|
|
|
|
assert app.state.iti_system is registry.get("iti_system")
|
|
assert "/auth/loginByPassword" in paths
|
|
assert "/sys/user/list" in paths
|
|
assert "/sys/user-attributes/current" in paths
|
|
assert "/sys/role/list" in paths
|
|
assert "/sys/menu/list" in paths
|
|
assert "/sys/dept/list" in paths
|
|
assert "/sys/config/list" in paths
|
|
assert "/sys/dict/type/page" in paths
|
|
assert "/sys/log/page" in paths
|
|
assert "/internal/audit/events" in paths
|
|
assert "/sys/file/{file_id}" in paths
|
|
assert "/upload" in paths
|
|
assert "/file/{file_id}/download" in paths
|
|
assert "system:user:list" in registry.permissions
|
|
|
|
|
|
def test_login_and_current_user_flow():
|
|
app = make_app()
|
|
client = TestClient(app)
|
|
headers = login(client)
|
|
|
|
response = client.get("/sys/user/current", headers=headers)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()["data"]
|
|
assert data["username"] == "admin"
|
|
assert data["roleCodes"] == ["ADMIN"]
|
|
assert data["isSuper"] is True
|
|
event = app.state.audit_dispatcher._queue.get_nowait()
|
|
assert event.type == "login"
|
|
assert event.actor_id == data["id"]
|
|
assert event.success is True
|
|
|
|
|
|
def test_admin_can_list_system_routes():
|
|
client = TestClient(make_app())
|
|
headers = login(client)
|
|
|
|
assert client.get("/sys/user/list", headers=headers).json()["code"] == 200
|
|
assert client.get("/sys/menu/tree", headers=headers).json()["code"] == 200
|
|
assert client.get("/sys/config/list", headers=headers).json()["code"] == 200
|
|
|
|
|
|
def test_menu_exists_supports_path_auth_code_and_excludes_current_id():
|
|
client = TestClient(make_app())
|
|
headers = login(client)
|
|
|
|
by_path = client.get("/sys/menu/exists", params={"path": "/system/menu"}, headers=headers)
|
|
same_path = client.get(
|
|
"/sys/menu/exists",
|
|
params={"id": "system-menu", "path": "/system/menu"},
|
|
headers=headers,
|
|
)
|
|
by_auth_code = client.get(
|
|
"/sys/menu/exists",
|
|
params={"authCode": "system:menu:list"},
|
|
headers=headers,
|
|
)
|
|
|
|
assert by_path.json()["data"] == {"exists": True}
|
|
assert same_path.json()["data"] == {"exists": False}
|
|
assert by_auth_code.json()["data"] == {"exists": True}
|
|
|
|
|
|
def test_menu_tree_returns_current_user_visible_routes_without_menu_admin_permission():
|
|
app = make_app()
|
|
with app.state.db_sessionmaker() as db:
|
|
parent = SysMenu(
|
|
id="orders",
|
|
name="Orders",
|
|
type="catalog",
|
|
path="/orders",
|
|
status="enabled",
|
|
)
|
|
child = SysMenu(
|
|
id="orders-list",
|
|
name="OrdersList",
|
|
type="menu",
|
|
path="/orders/list",
|
|
component="/orders/list",
|
|
auth_code="orders:list",
|
|
parent=parent,
|
|
status="enabled",
|
|
)
|
|
button = SysMenu(
|
|
id="orders-create",
|
|
name="OrdersCreate",
|
|
type="button",
|
|
auth_code="orders:create",
|
|
parent=child,
|
|
status="enabled",
|
|
)
|
|
role = Role(name="操作员", code="OPERATOR", menus=[child, button])
|
|
user = User(username="operator", status="enabled", roles=[role])
|
|
user.set_password("123456")
|
|
db.add_all([parent, child, button, role, user])
|
|
db.commit()
|
|
user_id = user.id
|
|
|
|
token = create_access_token(user_id, app.state.config)
|
|
response = TestClient(app).get(
|
|
"/sys/menu/tree",
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()["data"]
|
|
assert [item["id"] for item in data] == ["orders"]
|
|
assert [item["id"] for item in data[0]["children"]] == ["orders-list"]
|
|
assert data[0]["children"][0]["children"] == []
|
|
|
|
|
|
def test_internal_audit_writes_sys_log():
|
|
client = TestClient(make_app())
|
|
|
|
response = client.post(
|
|
"/internal/audit/events",
|
|
headers={"Authorization": "Bearer svc-token"},
|
|
json={
|
|
"events": [
|
|
{
|
|
"type": "OPERATION",
|
|
"title": "修改生产订单",
|
|
"actorId": "svc",
|
|
"actorType": "service",
|
|
"method": "POST",
|
|
"path": "/monitor/api/call",
|
|
"targetType": "order",
|
|
"targetId": "MO001",
|
|
"diff": {"qty": {"before": 1, "after": 2}},
|
|
}
|
|
]
|
|
},
|
|
)
|
|
|
|
assert response.json()["data"] == {"count": 1}
|
|
headers = login(client)
|
|
logs = client.get("/sys/log/page", headers=headers).json()["data"]["items"]
|
|
assert logs[0]["name"] == "修改生产订单"
|
|
assert logs[0]["userId"] == "svc"
|