mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
test: lift coverage 41% → 76% (+1068 tests across 36 files)
Service-level tests now exercise provider, permissions, project, journal, messaging, work_session, metrics, kanban, extraction, learning, notification, dashboard, llm_routing, a2a, task, repository_base, audit, db_seed, branch_name, indexed_document, query_helpers, agent. API route tests cover provider, journal, project, sessions, dashboard, work_session, tasks, a2a, groups, notifications, agents, channels, messages, kanban, api_resources. Pure-function helpers covered: handlers, deps_helpers, middleware, middleware_docs, transcription, pr templates, agents_config, errors, logging, journal/notification/channel/a2a access, task_lifecycle, streaming, converters, crypto, schemas (common + websocket), events, permissions extras. pyproject ruff per-file-ignores extended for tests so PLR2004 (status code magic values), PLC0415 (lazy imports), PLR0913 (fixture params), ARG001 (unused fixture deps), SIM105, and E501 don't fight test idioms.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"""api.middleware coverage — pure-function status mapping + handlers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.testclient import TestClient
|
||||
from roboco.api.middleware import (
|
||||
get_status_code,
|
||||
setup_middleware,
|
||||
)
|
||||
from roboco.exceptions import (
|
||||
AuthenticationError,
|
||||
InvalidStateError,
|
||||
NotFoundError,
|
||||
PermissionDeniedError,
|
||||
RobocoError,
|
||||
ValidationError,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_status_code
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_get_status_code_for_not_found() -> None:
|
||||
assert get_status_code(NotFoundError("Task", "abc")) == 404
|
||||
|
||||
|
||||
def test_get_status_code_for_validation() -> None:
|
||||
assert get_status_code(ValidationError("x")) == 422
|
||||
|
||||
|
||||
def test_get_status_code_for_invalid_state() -> None:
|
||||
assert get_status_code(InvalidStateError("pending", "complete")) == 409
|
||||
|
||||
|
||||
def test_get_status_code_for_permission() -> None:
|
||||
assert get_status_code(PermissionDeniedError("x")) == 403
|
||||
|
||||
|
||||
def test_get_status_code_for_auth() -> None:
|
||||
assert get_status_code(AuthenticationError("x")) == 401
|
||||
|
||||
|
||||
def test_get_status_code_for_generic() -> None:
|
||||
"""Unknown RobocoError subclass defaults to 400."""
|
||||
assert get_status_code(RobocoError("x", code="other")) == 400
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Middleware integration via TestClient
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _make_app() -> FastAPI:
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/ok")
|
||||
async def _ok():
|
||||
return {"status": "ok"}
|
||||
|
||||
@app.get("/raise")
|
||||
async def _raise():
|
||||
raise RuntimeError("boom")
|
||||
|
||||
@app.get("/notfound")
|
||||
async def _nf():
|
||||
raise NotFoundError("Resource", "abc")
|
||||
|
||||
@app.get("/http-error")
|
||||
async def _he():
|
||||
raise HTTPException(status_code=403, detail="nope")
|
||||
|
||||
setup_middleware(app)
|
||||
return app
|
||||
|
||||
|
||||
def test_middleware_adds_correlation_id_header() -> None:
|
||||
client = TestClient(_make_app())
|
||||
response = client.get("/ok")
|
||||
assert response.status_code == 200
|
||||
assert "X-Correlation-ID" in response.headers
|
||||
|
||||
|
||||
def test_middleware_uses_provided_correlation_id() -> None:
|
||||
client = TestClient(_make_app())
|
||||
cid = "test-correlation-12345"
|
||||
response = client.get("/ok", headers={"X-Correlation-ID": cid})
|
||||
assert response.headers["X-Correlation-ID"] == cid
|
||||
|
||||
|
||||
def test_middleware_adds_response_time_header() -> None:
|
||||
client = TestClient(_make_app())
|
||||
response = client.get("/ok")
|
||||
assert "X-Response-Time-Ms" in response.headers
|
||||
|
||||
|
||||
def test_roboco_exception_translates_to_404(caplog) -> None:
|
||||
client = TestClient(_make_app(), raise_server_exceptions=False)
|
||||
response = client.get("/notfound")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_http_exception_handler_returns_standardized_format() -> None:
|
||||
client = TestClient(_make_app(), raise_server_exceptions=False)
|
||||
response = client.get("/http-error")
|
||||
assert response.status_code == 403
|
||||
body = response.json()
|
||||
assert "error" in body
|
||||
|
||||
|
||||
def test_generic_exception_returns_500() -> None:
|
||||
client = TestClient(_make_app(), raise_server_exceptions=False)
|
||||
response = client.get("/raise")
|
||||
assert response.status_code == 500
|
||||
body = response.json()
|
||||
assert "error" in body
|
||||
Reference in New Issue
Block a user