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.
91 lines
2.6 KiB
Python
91 lines
2.6 KiB
Python
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
from app.api.v1.endpoints import leakage as leakage_endpoint
|
|
|
|
|
|
def _build_client() -> TestClient:
|
|
app = FastAPI()
|
|
app.include_router(leakage_endpoint.router, prefix="/api/v1")
|
|
app.dependency_overrides[leakage_endpoint.get_current_keycloak_username] = (
|
|
lambda: "tester"
|
|
)
|
|
return TestClient(app)
|
|
|
|
|
|
def test_identify_leakage_success(monkeypatch):
|
|
def fake_run_leakage_identification(**kwargs):
|
|
assert kwargs["network"] == "demo"
|
|
assert kwargs["username"] == "tester"
|
|
return {"rows": [], "area_count": 0}
|
|
|
|
monkeypatch.setattr(
|
|
leakage_endpoint, "run_leakage_identification", fake_run_leakage_identification
|
|
)
|
|
client = _build_client()
|
|
response = client.post(
|
|
"/api/v1/leakage-identifications",
|
|
json={
|
|
"network": "demo",
|
|
"scada_start": "2026-01-01T00:00:00+08:00",
|
|
"scada_end": "2026-01-01T01:00:00+08:00",
|
|
"scheme_name": "dma_001",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
assert response.json()["area_count"] == 0
|
|
|
|
|
|
def test_identify_leakage_rejects_server_file_paths():
|
|
client = _build_client()
|
|
|
|
response = client.post(
|
|
"/api/v1/leakage-identifications",
|
|
json={
|
|
"network": "demo",
|
|
"observed_pressure_data": "/etc/passwd",
|
|
"output_dir": "/tmp/leakage-results",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 422
|
|
|
|
|
|
def test_identify_leakage_rejects_unbounded_workload():
|
|
client = _build_client()
|
|
|
|
response = client.post(
|
|
"/api/v1/leakage-identifications",
|
|
json={
|
|
"network": "demo",
|
|
"observed_pressure_data": {"S1": [1.0]},
|
|
"pop_size": 1_000_000,
|
|
"max_gen": 1_000_000,
|
|
"n_workers": 10_000,
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 422
|
|
|
|
|
|
def test_identify_leakage_runs_service_outside_event_loop(monkeypatch):
|
|
captured = {}
|
|
|
|
async def fake_threadpool(func, **kwargs):
|
|
captured["func"] = func
|
|
captured["kwargs"] = kwargs
|
|
return {"rows": [], "area_count": 0}
|
|
|
|
monkeypatch.setattr(leakage_endpoint, "run_in_threadpool", fake_threadpool)
|
|
client = _build_client()
|
|
response = client.post(
|
|
"/api/v1/leakage-identifications",
|
|
json={
|
|
"network": "demo",
|
|
"observed_pressure_data": {"S1": [1.0]},
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert captured["func"] is leakage_endpoint.run_leakage_identification
|
|
assert captured["kwargs"]["username"] == "tester"
|