fix(gate): clear the xenon complexity failure + fixable test warnings

- transcript_retention.py: split select_prunable_transcripts into small helpers
  so the module averages complexity rank A (was B — failed make quality / xenon).
- pyproject: move the markers table from [tool.coverage.run] (coverage warned
  'Unrecognized option') to [tool.pytest.ini_options] where it belongs.
- HTTP_422_UNPROCESSABLE_ENTITY -> HTTP_422_UNPROCESSABLE_CONTENT (old name
  deprecated) in the tasks/product/settings routes + the validation middleware.
- conftest: drop pool_pre_ping on the per-test engine — pointless for a fresh
  per-test engine and it leaves an un-awaited asyncpg Connection._cancel
  coroutine that surfaced as a RuntimeWarning across ~30 integration tests.
This commit is contained in:
Renn F
2026-06-12 23:51:45 +02:00
parent 320499811b
commit b034c64177
7 changed files with 30 additions and 20 deletions
+6 -6
View File
@@ -237,6 +237,12 @@ testpaths = ["tests"]
python_files = ["test_*.py"]
asyncio_default_fixture_loop_scope = "function"
addopts = "--cov=roboco --cov-report=term-missing"
markers = [
"asyncio: mark tests as async",
"slow: marks tests as slow",
"integration: marks tests as integration tests",
"unit: marks tests as unit tests",
]
[tool.coverage.run]
# Coverage core. Python 3.12+ supports sys.monitoring; the legacy `pytrace`
@@ -286,12 +292,6 @@ omit = [
# Auto-generated migrations.
"alembic/*",
]
markers = [
"asyncio: mark tests as async",
"slow: marks tests as slow",
"integration: marks tests as integration tests",
"unit: marks tests as unit tests",
]
# =============================================================================
# Vulture Configuration
+1 -1
View File
@@ -389,7 +389,7 @@ async def request_validation_handler(request: Request, exc: Exception) -> JSONRe
if remediate is not None:
content["remediate"] = remediate
return JSONResponse(
status_code=http_status.HTTP_422_UNPROCESSABLE_ENTITY,
status_code=http_status.HTTP_422_UNPROCESSABLE_CONTENT,
content=content,
)
+2 -2
View File
@@ -74,7 +74,7 @@ async def create_product(
detail="Duplicate cell: each team may map to at most one project.",
) from e
raise HTTPException(
status.HTTP_422_UNPROCESSABLE_ENTITY,
status.HTTP_422_UNPROCESSABLE_CONTENT,
detail="Invalid cell mapping: project_id does not reference a project.",
) from e
return product_to_response(product)
@@ -131,7 +131,7 @@ async def update_product(
detail="Duplicate cell: each team may map to at most one project.",
) from e
raise HTTPException(
status.HTTP_422_UNPROCESSABLE_ENTITY,
status.HTTP_422_UNPROCESSABLE_CONTENT,
detail="Invalid cell mapping: project_id does not reference a project.",
) from e
return product_to_response(product)
+1 -1
View File
@@ -30,7 +30,7 @@ async def update_setting(
await service.set(key, data.value)
except SettingValidationError as exc:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)
) from exc
# Write route commits explicitly (get_db auto-commit is unreliable).
await db.commit()
+1 -1
View File
@@ -154,7 +154,7 @@ async def create_task(
agent_row = await get_agent_by_slug(db, data.assigned_to)
if agent_row is None:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
detail={
"error": {
"code": "ASSIGNEE_NOT_FOUND",
+15 -8
View File
@@ -37,6 +37,19 @@ def is_agent_owned_dir(dir_name: str, workspaces_root: str) -> bool:
return bool(encoded_root) and dir_name.startswith(encoded_root)
def _is_old_transcript(path: Path, cutoff_epoch: float) -> bool:
"""True if ``path`` is a regular .jsonl file last modified before the cutoff."""
try:
return path.is_file() and path.stat().st_mtime < cutoff_epoch
except OSError:
return False
def _old_transcripts_in(directory: Path, cutoff_epoch: float) -> list[Path]:
"""Old ``*.jsonl`` transcripts directly inside one agent-owned dir."""
return [t for t in directory.glob("*.jsonl") if _is_old_transcript(t, cutoff_epoch)]
def select_prunable_transcripts(
projects_root: Path, workspaces_root: str, cutoff_epoch: float
) -> list[Path]:
@@ -50,12 +63,6 @@ def select_prunable_transcripts(
return []
prunable: list[Path] = []
for child in sorted(projects_root.iterdir()):
if not child.is_dir() or not is_agent_owned_dir(child.name, workspaces_root):
continue
for transcript in child.glob("*.jsonl"):
try:
if transcript.is_file() and transcript.stat().st_mtime < cutoff_epoch:
prunable.append(transcript)
except OSError:
continue
if child.is_dir() and is_agent_owned_dir(child.name, workspaces_root):
prunable.extend(_old_transcripts_in(child, cutoff_epoch))
return prunable
+4 -1
View File
@@ -214,7 +214,10 @@ async def db_session(_test_database_url: str) -> AsyncIterator[AsyncSession]:
Each test gets its own session and connection; teardown rolls back any
uncommitted state and disposes the engine to keep connection counts low.
"""
engine = create_async_engine(_test_database_url, future=True, pool_pre_ping=True)
# No pool_pre_ping: a fresh per-test engine can't have stale connections, and
# pre-ping on asyncpg leaves an un-awaited Connection._cancel coroutine that
# surfaces as a RuntimeWarning during GC.
engine = create_async_engine(_test_database_url, future=True)
factory = async_sessionmaker(
bind=engine, class_=AsyncSession, expire_on_commit=False
)