Files
danyang-match/tests/e2e/v12_permission_scope_dashboard_test.py
T

119 lines
4.3 KiB
Python

"""V1.2 Task 6 权限/范围看板收口盘点。
默认在无活栈时运行静态/API contract 盘点,避免把不可达的真实 E2E 伪装成通过。
设置 ``E2E_CONTRACT_ONLY=1`` 后运行本文件;设置 ``E2E_BASE_URL`` 和
``E2E_OPERATOR_PASSWORD`` 并移除该变量,再运行同一文件才是活栈验收。
"""
from __future__ import annotations
import re
import os
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
BACKEND = ROOT / "backend" / "src" / "main" / "java"
ADMIN = ROOT / "admin-web" / "src"
WORKBENCH = BACKEND / "com/danyang/match/platform/matchmaker/dashboard"
pytestmark = pytest.mark.e2e
def _read(path: Path) -> str:
return path.read_text(encoding="utf-8")
def test_workbench_exposes_fixed_six_metrics_and_server_scope() -> None:
service = _read(WORKBENCH / "MatchmakerWorkbenchService.java")
controller = _read(WORKBENCH / "MatchmakerWorkbenchController.java")
for metric in (
"pendingReviews",
"matchmakerTodos",
"counselingTodos",
"serviceStationAppointments",
"effectiveMatches",
"activityRegistrations",
):
assert f'metrics.put("{metric}"' in service
assert '@RequestMapping("/operator/workbench")' in controller
assert '@GetMapping("/dashboard")' in controller
assert "operatorId" in service and "organizationId" in service
assert "@RequestParam" not in controller
def test_dashboard_does_not_accept_client_scope_overrides() -> None:
controller = _read(WORKBENCH / "MatchmakerWorkbenchController.java")
assert not re.search(r"(userId|matchmakerId|teamId|scope)\s*=", controller)
scope = _read(WORKBENCH / "MatchmakerDashboardScope.java")
assert '"ALL".equals(type)' in scope
assert '"TEAM".equals(type)' in scope
def test_admin_cards_have_stable_contract_keys_and_capability_status() -> None:
dashboard = _read(ADMIN / "services/dashboard.ts")
for key in (
"pending_reviews",
"matchmaker_todos",
"counseling_todos",
"station_appointments",
"effective_matches",
"activity_registrations",
):
assert f"key: '{key}'" in dashboard
assert "metricStatus(" in dashboard
assert "metricRoute(" in dashboard
assert "available" in dashboard
def test_task3_detail_scope_gap_is_detected_and_reported() -> None:
"""盘点当前缺口;详情统一 scope 不能被静态检查误报为已完成。"""
java_sources = "\n".join(
p.read_text(encoding="utf-8")
for p in BACKEND.rglob("*.java")
)
assert "class MatchmakerDashboardMapper" not in java_sources
assert "@RequestMapping(\"/operator/workbench/details\")" not in java_sources
def test_task3_gap_is_a_release_blocker() -> None:
report = ROOT / "docs" / "Phase-V12-Task3-Detail-Scope-Completion-Report.md"
text = _read(report)
assert "NOT_IMPLEMENTED" in text
assert "六类详情接口尚未统一" in text
@pytest.mark.skipif(os.environ.get("E2E_CONTRACT_ONLY") == "1", reason="contract-only: no live HTTP")
def test_user_cannot_access_operator_workbench(api, user_token) -> None:
response = api.call("GET", "/operator/workbench/dashboard", token=user_token)
assert response.status_code == 403, response.text[:400]
@pytest.mark.skipif(os.environ.get("E2E_CONTRACT_ONLY") == "1", reason="contract-only: no live HTTP")
def test_seeded_admin_dashboard_returns_six_metrics(api, admin_token) -> None:
data = api.ok("GET", "/operator/workbench/dashboard", token=admin_token)
assert data["scope"] == "ALL"
metrics = data["metrics"]
assert set(metrics) >= {
"pendingReviews",
"matchmakerTodos",
"counselingTodos",
"serviceStationAppointments",
"effectiveMatches",
"activityRegistrations",
}
for item in metrics.values():
assert "available" in item
assert "detailPermission" in item
@pytest.mark.skipif(os.environ.get("E2E_CONTRACT_ONLY") == "1", reason="contract-only: no live HTTP")
def test_matchmaker_dashboard_scope_is_server_derived(api, operator_token) -> None:
data = api.ok(
"GET",
"/operator/workbench/dashboard",
token=operator_token("e2e_matchmaker"),
params={"operatorId": "not-the-token-owner", "teamId": "other-team"},
)
assert data["scope"] in {"SELF", "TEAM"}