Files
TJWaterServerBinary/tests/api/test_sensor_placement_endpoints.py
T
jiang 5966d039de refactor(backend)!: separate algorithm and data layers
Reorganize algorithm packages by business responsibility, move orchestration into services, and keep database access behind pooled repositories.

Harden analysis API validation, remove unsafe legacy simulation endpoints, and add regression and architecture boundary coverage.

BREAKING CHANGE: legacy algorithm module paths and obsolete simulation endpoints are removed.
2026-09-04 17:30:55 +08:00

129 lines
3.7 KiB
Python

from datetime import datetime, timezone
from io import BytesIO
from types import SimpleNamespace
from uuid import uuid4
from fastapi.testclient import TestClient
from app.api.v1.endpoints import sensor_placement as endpoint
from tests.conftest import build_test_app
RUN_ID = uuid4()
def _run(**overrides):
value = {
"run_id": RUN_ID,
"name": "北区测压点",
"sensor_count": 1,
"min_diameter": 300,
"created_by": "alice",
"created_at": datetime(2026, 8, 24, tzinfo=timezone.utc),
"status": "completed",
"sensor_locations": ["J1"],
"sensor_points": [
{
"node_id": "J1",
"max_pipe_diameter": 400.0,
"project_x": 1.0,
"project_y": 2.0,
"map_x": 3.0,
"map_y": 4.0,
"longitude": 121.0,
"latitude": 31.0,
"elevation": 5.0,
}
],
}
value.update(overrides)
return value
def _client(project_role="member", username="alice", role="user"):
app = build_test_app(endpoint.router, "/api/v1")
app.dependency_overrides[endpoint.get_project_context] = lambda: SimpleNamespace(
project_code="tjwater", project_role=project_role
)
app.dependency_overrides[endpoint.get_current_metadata_user] = lambda: SimpleNamespace(
username=username, role=role, is_superuser=False
)
return TestClient(app)
def test_optimize_returns_analysis_run(monkeypatch):
captured = {}
monkeypatch.setattr(
endpoint,
"optimize_sensor_placement_by_kmeans",
lambda **kwargs: captured.update(kwargs) or {"run_id": RUN_ID},
)
monkeypatch.setattr(endpoint, "get_sensor_placement_run", lambda *_: _run())
response = _client().post(
"/api/v1/sensor-placement-runs",
json={
"network": "tjwater",
"run_name": "北区测压点",
"sensor_type": "pressure",
"method": "kmeans",
"sensor_count": 1,
"min_diameter": 300,
},
)
assert response.status_code == 200
assert response.json()["run_id"] == str(RUN_ID)
assert captured["created_by"] == "alice"
def test_optimize_rejects_project_mismatch(monkeypatch):
response = _client().post(
"/api/v1/sensor-placement-runs",
json={
"network": "other",
"run_name": "越权运行",
"sensor_type": "pressure",
"method": "kmeans",
"sensor_count": 1,
},
)
assert response.status_code == 403
def test_viewer_cannot_update_run(monkeypatch):
monkeypatch.setattr(endpoint, "get_sensor_placement_run", lambda *_: _run())
response = _client(project_role="viewer").put(
f"/api/v1/sensor-placement-runs/{RUN_ID}",
params={"network": "tjwater"},
json={
"expected_sensor_locations": ["J1"],
"sensor_locations": ["J2"],
},
)
assert response.status_code == 403
def test_export_returns_xlsx(monkeypatch):
monkeypatch.setattr(endpoint, "get_sensor_placement_run", lambda *_: _run())
monkeypatch.setattr(
endpoint,
"build_sensor_placement_workbook",
lambda **kwargs: BytesIO(b"xlsx"),
)
response = _client().post(
f"/api/v1/sensor-placement-runs/{RUN_ID}/exports/excel",
params={"network": "tjwater"},
json={"sensor_locations": ["J1"], "adjustment_status": {}},
)
assert response.status_code == 200
assert response.content == b"xlsx"
assert response.headers["content-type"].startswith(
"application/vnd.openxmlformats-officedocument"
)