test: defend the live session and the server API
test_live: a product is unconfirmed, then labelled, without blocking frames; a
weak match never names a SKU and ends as needs_confirmation; a restarted id is
re-verified rather than inheriting its old label; inherited labels are flagged;
latest-frame-wins accounting; payload shape; glare hint; per-session
classification cap; a method change clears labels and drops late results.
test_api: health, catalog shape (no local paths), template-image 404s including
traversal-looking ids, stock validation, recognize over raw bytes and multipart,
415/422/400 with one error shape, classify/inspect roundtrip, IMAGE_EXPIRED,
token auth on REST and the socket, the WebSocket protocol (hello, frames,
config/reset/ping, recoverable BAD_FRAME and UNDECODABLE_IMAGE, inspect and
TRACK_NOT_IN_VIEW), the session limit, and the legacy session route.
Server fixes the tests forced out: FastAPI validation errors now use the same
{"error": {...}} envelope as every other failure, and the legacy session routes
accept a missing JSON body instead of returning 500.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
90cce64cc8
commit
da2bed1c35
3 changed files with 637 additions and 4 deletions
|
|
@ -51,6 +51,7 @@ from fastapi import ( # noqa: E402
|
|||
WebSocket,
|
||||
WebSocketDisconnect,
|
||||
)
|
||||
from fastapi.exceptions import RequestValidationError # noqa: E402
|
||||
from fastapi.middleware.cors import CORSMiddleware # noqa: E402
|
||||
from fastapi.responses import FileResponse, JSONResponse, Response # noqa: E402
|
||||
from fastapi.staticfiles import StaticFiles # noqa: E402
|
||||
|
|
@ -562,7 +563,8 @@ def create_app(
|
|||
|
||||
# ---------------------------------------------------------------- legacy session routes
|
||||
@app.post("/api/sessions")
|
||||
async def legacy_start_session(payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def legacy_start_session(payload: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
payload = payload or {}
|
||||
session = scans.start_session(
|
||||
zone_id=str(payload.get("zone_id") or "zone-1"),
|
||||
catalog_version=str(payload.get("catalog_version") or "stock-v1"),
|
||||
|
|
@ -571,9 +573,10 @@ def create_app(
|
|||
return {"status": "success", "session": session.snapshot()}
|
||||
|
||||
@app.post("/api/sessions/{session_id}/frames")
|
||||
async def legacy_observe_frame(session_id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def legacy_observe_frame(session_id: str, payload: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
from observe import FrameRequest, ObserveError
|
||||
|
||||
payload = payload or {}
|
||||
try:
|
||||
session = scans.get_session(session_id)
|
||||
raw = payload.get("frame_bytes_b64") or payload.get("image_base64") or ""
|
||||
|
|
@ -608,9 +611,11 @@ def create_app(
|
|||
raise _error("NOT_FOUND", str(exc), 404)
|
||||
|
||||
@app.post("/api/sessions/{session_id}/review")
|
||||
async def legacy_review(session_id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def legacy_review(session_id: str, payload: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
from observe import ObserveError
|
||||
|
||||
payload = payload or {}
|
||||
|
||||
try:
|
||||
snapshot = scans.review(
|
||||
session_id,
|
||||
|
|
@ -650,9 +655,10 @@ def create_app(
|
|||
raise _error("CONFLICT", str(exc), 409)
|
||||
|
||||
@app.post("/api/compare_live_vs_still")
|
||||
async def legacy_compare(payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def legacy_compare(payload: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
from observe import ObserveError
|
||||
|
||||
payload = payload or {}
|
||||
try:
|
||||
comparison = scans.compare_live_vs_still(
|
||||
str(payload.get("sampled_session_id")), str(payload.get("still_session_id"))
|
||||
|
|
@ -662,6 +668,16 @@ def create_app(
|
|||
raise _error("CONFLICT", str(exc), 409)
|
||||
|
||||
# ---------------------------------------------------------------- errors
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
|
||||
first = (exc.errors() or [{}])[0]
|
||||
location = ".".join(str(part) for part in first.get("loc", []) if part != "body")
|
||||
message = first.get("msg", "invalid request")
|
||||
detail = f"{location}: {message}" if location else message
|
||||
return JSONResponse(
|
||||
status_code=422, content={"error": {"code": "VALIDATION_ERROR", "message": detail}}
|
||||
)
|
||||
|
||||
@app.exception_handler(HTTPException)
|
||||
async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
|
||||
detail = exc.detail
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue