Dev login no longer honors injected roles; self-service simulate/convert blocks R4 disclosure grades like the UI and chat already do. Reuse of a convert idempotency key with a different body returns 409. CT6 redeem qty is derived from T+2 lots instead of a fixed 35000; CT7-05 and CT10-07 assertions follow. Update project memory and v1.1 post-fix test artifacts.
111 lines
3.4 KiB
Python
111 lines
3.4 KiB
Python
"""Wave 0:JWT / RBAC 测试(AL-09 接线后:login 走 auth_service,chat 走模块 T-06)。"""
|
||
|
||
from app.gateway.jwt_service import decode_token, issue_token
|
||
from app.service.auth_service import verify_token
|
||
|
||
|
||
def test_issue_and_decode_staff_token():
|
||
token, expires = issue_token("STAFF-20001", "staff")
|
||
assert expires > 0
|
||
payload = decode_token(token)
|
||
assert payload["sub"] == "STAFF-20001"
|
||
assert "analyst" in payload["roles"]
|
||
assert "agent:analyst:chat" in payload["permissions"]
|
||
# login 与 risk/chat 共用模块验签
|
||
claims = verify_token(token)
|
||
assert claims.sub == "STAFF-20001"
|
||
|
||
|
||
def test_login_endpoint(client):
|
||
resp = client.post("/api/auth/login", json={"actor_id": "STAFF-20001", "token_type": "staff"})
|
||
assert resp.status_code == 200
|
||
body = resp.json()
|
||
assert body["code"] == 0
|
||
assert "access_token" in body["data"]
|
||
assert "trace_id" in body
|
||
|
||
|
||
def test_login_ignores_injected_roles(client):
|
||
"""CT-001 P0:请求体 roles 不得提权(仍按 DEFAULT_ROLES_BY_ACTOR)。"""
|
||
resp = client.post(
|
||
"/api/auth/login",
|
||
json={
|
||
"actor_id": "STAFF-10087",
|
||
"token_type": "staff",
|
||
"roles": ["risk_officer"],
|
||
},
|
||
)
|
||
assert resp.status_code == 200
|
||
roles = resp.json()["data"]["roles"]
|
||
assert roles == ["advisor"]
|
||
assert "risk_officer" not in roles
|
||
|
||
|
||
def test_login_unknown_actor_still_rejected_with_injected_roles(client):
|
||
resp = client.post(
|
||
"/api/auth/login",
|
||
json={
|
||
"actor_id": "STAFF-99999",
|
||
"token_type": "staff",
|
||
"roles": ["advisor", "risk_demo"],
|
||
},
|
||
)
|
||
assert resp.status_code == 401
|
||
|
||
|
||
def test_chat_requires_auth(client):
|
||
resp = client.post(
|
||
"/api/chat",
|
||
json={"message": "hello"},
|
||
headers={"X-Agent-Type": "analyst"},
|
||
)
|
||
assert resp.status_code == 401
|
||
|
||
|
||
def test_chat_analyst_ok(client):
|
||
login = client.post("/api/auth/login", json={"actor_id": "STAFF-20001", "token_type": "staff"})
|
||
token = login.json()["data"]["access_token"]
|
||
resp = client.post(
|
||
"/api/chat",
|
||
json={"message": "上季度收益率"},
|
||
headers={
|
||
"Authorization": f"Bearer {token}",
|
||
"X-Agent-Type": "analyst",
|
||
},
|
||
)
|
||
assert resp.status_code == 200
|
||
body = resp.json()
|
||
assert body["agent_type"] == "analyst"
|
||
assert "reply" in body
|
||
assert body["session_id"].startswith("sess-")
|
||
|
||
|
||
def test_agent_type_mismatch_forbidden(client):
|
||
login = client.post("/api/auth/login", json={"actor_id": "STAFF-20001", "token_type": "staff"})
|
||
token = login.json()["data"]["access_token"]
|
||
resp = client.post(
|
||
"/api/chat",
|
||
json={"message": "hello"},
|
||
headers={
|
||
"Authorization": f"Bearer {token}",
|
||
"X-Agent-Type": "customer",
|
||
},
|
||
)
|
||
assert resp.status_code == 403
|
||
|
||
|
||
def test_advisor_not_assigned_forbidden(client, mock_db):
|
||
mock_db["core_ro"].is_advisor_assigned.return_value = False
|
||
login = client.post("/api/auth/login", json={"actor_id": "STAFF-10086", "token_type": "staff"})
|
||
token = login.json()["data"]["access_token"]
|
||
resp = client.post(
|
||
"/api/chat",
|
||
json={"message": "查持仓", "customer_id": "CUST-1010"},
|
||
headers={
|
||
"Authorization": f"Bearer {token}",
|
||
"X-Agent-Type": "advisor",
|
||
},
|
||
)
|
||
assert resp.status_code == 403
|
||
assert mock_db["audit"].insert.called
|