fix(metadata): fail loudly when the sidecar write cannot land

update_model_metadata ignored the save_metadata result, so a fetch on a
drive that went offline mid-run was counted as success: the cache got
the fresh CivitAI payload while the durable sidecar never landed, and
every later fetch skipped the model as already fetched.

The save result now raises MetadataPersistError. In the bulk fetch path
this fails the item (reported in the summary, cache untouched, model
stays eligible for the next run); relink surfaces an honest 500.

Also corrects the stale known-limitation note in the scoped-scan plan:
a startup-offline root has been listed and re-admitted since Wave 6.
This commit is contained in:
Will Miao
2026-10-08 07:55:47 +08:00
parent 95acd6308a
commit acefe1a348
4 changed files with 71 additions and 5 deletions
+51 -1
View File
@@ -5,7 +5,7 @@ from unittest.mock import AsyncMock
import pytest
from py.services.connectivity_guard import OFFLINE_COOLDOWN_ERROR, OFFLINE_FRIENDLY_MESSAGE
from py.services.errors import RateLimitError
from py.services.errors import MetadataPersistError, RateLimitError
from py.services.metadata_sync_service import MetadataSyncService, _merge_ordered_unique
@@ -285,6 +285,56 @@ async def test_fetch_and_update_model_success_updates_cache(tmp_path):
update_cache.assert_awaited_once()
@pytest.mark.asyncio
async def test_update_model_metadata_raises_when_sidecar_write_fails():
"""A failed sidecar write must surface instead of being swallowed."""
helpers = build_service()
helpers.metadata_manager.save_metadata.return_value = False
with pytest.raises(MetadataPersistError):
await helpers.service.update_model_metadata(
"path/to/model.metadata.json",
{"civitai": {}, "model_name": "Local"},
{"source": "api", "model": {"name": "Remote"}, "images": []},
helpers.default_provider,
)
@pytest.mark.asyncio
async def test_fetch_and_update_model_reports_sidecar_write_failure(tmp_path):
"""Fetch fails the item (and leaves the cache untouched) when the sidecar
cannot be written, so the model stays eligible for the next run."""
helpers = build_service()
helpers.metadata_manager.save_metadata.return_value = False
civitai_payload = {
"source": "api",
"model": {"name": "Remote", "description": "", "tags": ["tag"]},
"images": [],
"baseModel": "sdxl",
}
helpers.default_provider.get_model_by_hash.return_value = (civitai_payload, None)
model_path = tmp_path / "model.safetensors"
model_data: Dict[str, Any] = {
"model_name": "Local",
"folder": "root",
"file_path": str(model_path),
}
update_cache = AsyncMock(return_value=True)
ok, error = await helpers.service.fetch_and_update_model(
sha256="abc",
file_path=str(model_path),
model_data=model_data,
update_cache_func=update_cache,
)
assert ok is False
assert "sidecar" in (error or "")
update_cache.assert_not_awaited()
@pytest.mark.asyncio
async def test_fetch_and_update_model_keeps_deleted_flag_false_for_archive_source(tmp_path):
helpers = build_service()