fix(sensor-placement): enforce project write boundaries
Bind every scheme request to ProjectContext, keep viewer access read-only, reject concurrent optimization jobs without blocking worker threads, and cap export/update payload sizes. Run optimization and workbook work off the event loop.
This commit is contained in:
@@ -81,8 +81,9 @@ def _load_module(monkeypatch):
|
||||
)
|
||||
|
||||
class ProjectContext:
|
||||
def __init__(self, project_code: str):
|
||||
def __init__(self, project_code: str, project_role: str = "member"):
|
||||
self.project_code = project_code
|
||||
self.project_role = project_role
|
||||
|
||||
async def project_context():
|
||||
return ProjectContext("tjwater")
|
||||
@@ -127,7 +128,7 @@ def _load_module(monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
def _client(module, user=None):
|
||||
def _client(module, user=None, project_role="member"):
|
||||
app = build_test_app(module.router, "/api/v1")
|
||||
if user is None:
|
||||
user = SimpleNamespace(
|
||||
@@ -137,7 +138,7 @@ def _client(module, user=None):
|
||||
)
|
||||
app.dependency_overrides[module.get_current_metadata_user] = lambda: user
|
||||
app.dependency_overrides[module.get_project_context] = lambda: (
|
||||
module.ProjectContext("tjwater")
|
||||
module.ProjectContext("tjwater", project_role)
|
||||
)
|
||||
return TestClient(app)
|
||||
|
||||
@@ -236,6 +237,73 @@ def test_optimize_rejects_unbounded_sensor_count(monkeypatch):
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
def test_optimize_rejects_viewer_project_role(monkeypatch):
|
||||
module = _load_module(monkeypatch)
|
||||
response = _client(module, project_role="viewer").post(
|
||||
"/api/v1/sensor-placement-schemes/optimize",
|
||||
json={
|
||||
"network": "tjwater",
|
||||
"scheme_name": "只读成员方案",
|
||||
"sensor_type": "pressure",
|
||||
"method": "kmeans",
|
||||
"sensor_count": 2,
|
||||
"min_diameter": 300,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_project_owner_and_admin_can_optimize(monkeypatch):
|
||||
module = _load_module(monkeypatch)
|
||||
for project_role in ("owner", "admin"):
|
||||
response = _client(module, project_role=project_role).post(
|
||||
"/api/v1/sensor-placement-schemes/optimize",
|
||||
json={
|
||||
"network": "tjwater",
|
||||
"scheme_name": f"{project_role}方案",
|
||||
"sensor_type": "pressure",
|
||||
"method": "kmeans",
|
||||
"sensor_count": 2,
|
||||
"min_diameter": 300,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_optimize_maps_running_project_job_to_409(monkeypatch):
|
||||
module = _load_module(monkeypatch)
|
||||
|
||||
def conflict(**kwargs):
|
||||
raise ConflictError("当前项目已有监测点优化任务正在运行,请稍后重试")
|
||||
|
||||
monkeypatch.setattr(module, "pressure_sensor_placement_kmeans", conflict)
|
||||
response = _client(module).post(
|
||||
"/api/v1/sensor-placement-schemes/optimize",
|
||||
json={
|
||||
"network": "tjwater",
|
||||
"scheme_name": "并发方案",
|
||||
"sensor_type": "pressure",
|
||||
"method": "kmeans",
|
||||
"sensor_count": 2,
|
||||
"min_diameter": 300,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 409
|
||||
|
||||
|
||||
def test_viewer_reads_scheme_as_non_editable(monkeypatch):
|
||||
module = _load_module(monkeypatch)
|
||||
response = _client(module, project_role="viewer").get(
|
||||
"/api/v1/sensor-placement-schemes/7",
|
||||
params={"network": "tjwater"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["can_edit"] is False
|
||||
|
||||
|
||||
def test_update_rejects_non_owner(monkeypatch):
|
||||
module = _load_module(monkeypatch)
|
||||
response = _client(
|
||||
@@ -253,6 +321,20 @@ def test_update_rejects_non_owner(monkeypatch):
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_update_rejects_owner_with_viewer_project_role(monkeypatch):
|
||||
module = _load_module(monkeypatch)
|
||||
response = _client(module, project_role="viewer").put(
|
||||
"/api/v1/sensor-placement-schemes/7",
|
||||
params={"network": "tjwater"},
|
||||
json={
|
||||
"expected_sensor_location": ["J1", "J2"],
|
||||
"sensor_location": ["J1", "J3"],
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_admin_can_overwrite_scheme(monkeypatch):
|
||||
module = _load_module(monkeypatch)
|
||||
response = _client(
|
||||
|
||||
Reference in New Issue
Block a user