test: record v1.2 permission scope acceptance gaps
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
"""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"}
|
||||
Reference in New Issue
Block a user