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, "pressure_sensor_placement_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["username"] == "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" )