fix(runtime): fail closed on offline artifact misses
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from k1link.artifact_gateway import (
|
||||
ArtifactGateway,
|
||||
CentralArtifactStore,
|
||||
LocalArtifactCache,
|
||||
)
|
||||
from k1link.sessions.recording import SessionRecordingMaterializer
|
||||
from k1link.web.runtime_readiness import (
|
||||
BackgroundReconcilerReadiness,
|
||||
build_runtime_readiness,
|
||||
)
|
||||
|
||||
|
||||
def _document(
|
||||
tmp_path: Path,
|
||||
*,
|
||||
gateway: ArtifactGateway | None,
|
||||
reconciler: BackgroundReconcilerReadiness,
|
||||
) -> dict[str, object]:
|
||||
return build_runtime_readiness(
|
||||
version="test",
|
||||
plugin_runtime_health=({"status": "ready"},),
|
||||
recording_materializer=SessionRecordingMaterializer(
|
||||
tmp_path / "data",
|
||||
exporter=lambda _source, _destination: {},
|
||||
free_space_reserve_bytes=0,
|
||||
),
|
||||
artifact_gateway=gateway,
|
||||
map_gateway_configured=False,
|
||||
ffmpeg_available=True,
|
||||
ffprobe_available=True,
|
||||
reconciler=reconciler,
|
||||
)
|
||||
|
||||
|
||||
def test_runtime_readiness_reports_shared_store_and_bounded_cache(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
central_root = tmp_path / "central"
|
||||
gateway = ArtifactGateway(
|
||||
CentralArtifactStore(central_root, create=True),
|
||||
LocalArtifactCache(
|
||||
tmp_path / "cache",
|
||||
max_bytes=1024 * 1024,
|
||||
free_space_reserve_bytes=0,
|
||||
),
|
||||
)
|
||||
reconciler = BackgroundReconcilerReadiness()
|
||||
reconciler.record_success()
|
||||
|
||||
document = _document(tmp_path, gateway=gateway, reconciler=reconciler)
|
||||
|
||||
assert document["ok"] is True
|
||||
assert document["status"] == "ok"
|
||||
assert document["readiness"] == {
|
||||
"operational": True,
|
||||
"production_shared_artifacts": True,
|
||||
}
|
||||
components = document["components"]
|
||||
assert isinstance(components, dict)
|
||||
assert components["recording_cache"]["max_bytes"] == 8 * 1024 * 1024 * 1024
|
||||
assert components["artifact_store"]["status"] == "ready"
|
||||
assert components["map_gateway"]["status"] == "optional"
|
||||
|
||||
|
||||
def test_runtime_readiness_exposes_offline_cache_only_mode(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
central_root = tmp_path / "central"
|
||||
gateway = ArtifactGateway(
|
||||
CentralArtifactStore(central_root, create=True),
|
||||
LocalArtifactCache(
|
||||
tmp_path / "cache",
|
||||
max_bytes=1024 * 1024,
|
||||
free_space_reserve_bytes=0,
|
||||
),
|
||||
)
|
||||
central_root.rename(tmp_path / "central-offline")
|
||||
reconciler = BackgroundReconcilerReadiness()
|
||||
reconciler.record_success()
|
||||
|
||||
document = _document(tmp_path, gateway=gateway, reconciler=reconciler)
|
||||
|
||||
assert document["ok"] is True
|
||||
assert document["status"] == "degraded"
|
||||
assert document["readiness"] == {
|
||||
"operational": True,
|
||||
"production_shared_artifacts": False,
|
||||
}
|
||||
components = document["components"]
|
||||
assert isinstance(components, dict)
|
||||
assert components["artifact_store"]["status"] == "offline-cache-only"
|
||||
|
||||
|
||||
def test_runtime_readiness_fails_after_repeated_reconciler_errors(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
reconciler = BackgroundReconcilerReadiness()
|
||||
for _ in range(3):
|
||||
reconciler.record_failure(OSError("private detail"))
|
||||
|
||||
document = _document(tmp_path, gateway=None, reconciler=reconciler)
|
||||
|
||||
assert document["ok"] is False
|
||||
assert document["status"] == "unavailable"
|
||||
components = document["components"]
|
||||
assert isinstance(components, dict)
|
||||
assert components["background_reconciler"]["last_failure_type"] == "OSError"
|
||||
assert "private detail" not in str(document)
|
||||
Reference in New Issue
Block a user