"""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"}