GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
152 lines
4.4 KiB
Python
152 lines
4.4 KiB
Python
"""Regression coverage for bounded, stable segmentation result listings."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime
|
|
from types import SimpleNamespace
|
|
from uuid import UUID
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.api.routes import segmentation as segmentation_routes
|
|
from app.db.session import get_db
|
|
from app.schemas.segmentation import SegmentationListResponse
|
|
from app.services.segmentation_service import SegmentationService
|
|
|
|
|
|
RUN_ID = UUID("00000000-0000-0000-0000-000000000101")
|
|
DATASET_ID = UUID("00000000-0000-0000-0000-000000000102")
|
|
PROJECT_ID = UUID("00000000-0000-0000-0000-000000000103")
|
|
|
|
|
|
def _segmentation(index: int) -> SimpleNamespace:
|
|
return SimpleNamespace(
|
|
id=UUID(int=index + 1),
|
|
project_id=PROJECT_ID,
|
|
dataset_id=DATASET_ID,
|
|
analysis_run_id=RUN_ID,
|
|
job_id=None,
|
|
model_name="segmentation-test-model",
|
|
model_version="1",
|
|
class_name="building",
|
|
confidence=0.99 - index / 100,
|
|
bbox_json=None,
|
|
area_m2=float(index + 1),
|
|
mask_path=None,
|
|
source_tile_path=None,
|
|
tile_index=index,
|
|
properties_json={},
|
|
provenance_json={},
|
|
created_at=datetime(2026, 8, 23, tzinfo=UTC),
|
|
)
|
|
|
|
|
|
class _Session:
|
|
def get(self, _model, identifier):
|
|
if identifier == RUN_ID:
|
|
return SimpleNamespace(analysis_type="segmentation")
|
|
return None
|
|
|
|
|
|
def test_service_returns_one_stable_page_with_complete_metadata(monkeypatch) -> None:
|
|
rows = [_segmentation(index) for index in range(5)]
|
|
monkeypatch.setattr(
|
|
SegmentationService,
|
|
"_query_segmentation_rows",
|
|
staticmethod(lambda _db, **_filters: rows),
|
|
)
|
|
|
|
result = SegmentationService.list_segmentations(
|
|
_Session(),
|
|
analysis_run_id=RUN_ID,
|
|
dataset_id=DATASET_ID,
|
|
limit=2,
|
|
offset=1,
|
|
)
|
|
|
|
assert [item.id for item in result.items] == [rows[1].id, rows[2].id]
|
|
assert result.total == 5
|
|
assert result.limit == 2
|
|
assert result.offset == 1
|
|
assert result.truncated is True
|
|
|
|
|
|
def test_service_pages_cover_the_stably_ordered_population_once(monkeypatch) -> None:
|
|
rows = [_segmentation(index) for index in range(5)]
|
|
monkeypatch.setattr(
|
|
SegmentationService,
|
|
"_query_segmentation_rows",
|
|
staticmethod(lambda _db, **_filters: rows),
|
|
)
|
|
|
|
seen = []
|
|
for offset in (0, 2, 4):
|
|
result = SegmentationService.list_segmentations(
|
|
_Session(),
|
|
dataset_id=DATASET_ID,
|
|
limit=2,
|
|
offset=offset,
|
|
)
|
|
seen.extend(item.id for item in result.items)
|
|
assert result.total == len(rows)
|
|
assert result.offset == offset
|
|
|
|
assert seen == [row.id for row in rows]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("path", "expected_run_id", "expected_dataset_id"),
|
|
[
|
|
(f"/api/v1/segmentation/runs/{RUN_ID}/segmentations", RUN_ID, None),
|
|
(f"/api/v1/segmentation/datasets/{DATASET_ID}/segmentations", None, DATASET_ID),
|
|
],
|
|
)
|
|
def test_both_listing_routes_forward_the_page_window_and_return_it(
|
|
monkeypatch,
|
|
path: str,
|
|
expected_run_id: UUID | None,
|
|
expected_dataset_id: UUID | None,
|
|
) -> None:
|
|
calls: list[dict] = []
|
|
|
|
def _list(_db, analysis_run_id=None, **parameters):
|
|
calls.append({"analysis_run_id": analysis_run_id, **parameters})
|
|
return SegmentationListResponse(
|
|
items=[],
|
|
total=9,
|
|
limit=2,
|
|
offset=4,
|
|
truncated=True,
|
|
)
|
|
|
|
monkeypatch.setattr(SegmentationService, "list_segmentations", staticmethod(_list))
|
|
app = FastAPI()
|
|
app.include_router(segmentation_routes.router, prefix="/api/v1")
|
|
app.dependency_overrides[get_db] = lambda: object()
|
|
|
|
response = TestClient(app).get(
|
|
path,
|
|
params={"limit": 2, "offset": 4, "class_name": "building", "min_confidence": 0.5},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["data"] == {
|
|
"items": [],
|
|
"total": 9,
|
|
"limit": 2,
|
|
"offset": 4,
|
|
"truncated": True,
|
|
}
|
|
assert calls == [
|
|
{
|
|
"analysis_run_id": expected_run_id,
|
|
"limit": 2,
|
|
"offset": 4,
|
|
"dataset_id": expected_dataset_id,
|
|
"class_name": "building",
|
|
"min_confidence": 0.5,
|
|
}
|
|
]
|