Handle expected_minutes=0 gracefully (defense-in-depth) #61

Merged
sascha merged 2 commits from fix/media-verify-runtime-zero-handling into main 2026-09-18 07:19:45 +02:00
2 changed files with 18 additions and 3 deletions

19
app.py
View file

@ -12,7 +12,7 @@ from contextlib import asynccontextmanager
from contextvars import ContextVar
log = logging.getLogger("butler")
VERSION = "2.4.8"
VERSION = "2.4.9"
API_DIR = os.environ.get("API_KEY_DIR", "/data/api")
VAULT_CACHE_DIR = os.environ.get("VAULT_CACHE_DIR", "/data/vault-cache")
@ -1558,7 +1558,7 @@ MEDIA_VERIFY_PROBES = {
class MediaVerifyRequest(BaseModel):
host: str = Field(..., max_length=64)
path: str = Field(..., max_length=1000)
expected_minutes: float | None = Field(None, gt=0, le=1440)
expected_minutes: float | None = Field(None, ge=0, le=1440)
tolerance_pct: float = Field(20.0, gt=0, le=100)
@ -1588,6 +1588,21 @@ async def media_verify(payload: MediaVerifyRequest, _=Depends(_verify)):
if not target:
raise HTTPException(404, f"Host {payload.host} not found in inventory")
# expected_minutes == 0 or None: no reference runtime available, skip verification gracefully
if not payload.expected_minutes or payload.expected_minutes <= 0:
return {
"host": payload.host,
"path": payload.path,
"duration_seconds": None,
"duration_minutes": None,
"expected_minutes": payload.expected_minutes,
"verified": False,
"suspect": False,
"skipped": True,
"skip_reason": "no_reference_runtime",
"deviation_pct": None,
}
probe_cmd = (
f"docker exec {container} ffprobe -v error "
f"-show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {shlex.quote(payload.path)}"

View file

@ -1006,7 +1006,7 @@ def test_media_verify_returns_502_on_unparsable_ffprobe_output(monkeypatch):
response = client.post(
"/media/verify",
headers={"Authorization": "Bearer test-token"},
json={"host": "arrapps", "path": "/data/UHD/serien/ep.mkv"},
json={"host": "arrapps", "path": "/data/UHD/serien/ep.mkv", "expected_minutes": 49.5},
)
assert response.status_code == 502