feat(service-storage-harness): slice 4 — cascade-delete wiring + integration test

Wire ServiceDataHarness.cascade_delete into SettingsStore.delete_service
(best-effort try/except, logs on failure). Fix migration runner to also
catch 'no such table' on fresh DBs (ALTER TABLE before init_schema).
Integration test proves end-to-end cascade across both concerns (qBit
samples + media items) with multi-instance preservation.

Backend: 322 pytest pass, ruff clean.
This commit is contained in:
Developer
2026-07-09 09:11:42 +00:00
parent c87f398e37
commit 75c949ad25
4 changed files with 99 additions and 22 deletions
@@ -95,8 +95,9 @@ class ServiceDataHarness:
try:
conn.execute(stmt)
except sqlite3.OperationalError as exc:
if "duplicate column name" in str(exc).lower():
logger.debug("Skipping already-applied migration: %s", stmt[:80])
lowered = str(exc).lower()
if "duplicate column name" in lowered or "no such table" in lowered:
logger.debug("Skipping migration (already applied or table absent): %s", stmt[:80])
else:
raise
conn.commit()
@@ -1740,7 +1740,7 @@ class SettingsStore:
return self.get_service(service["id"]) or service
def delete_service(self, service_id: str) -> None:
"""Delete a service and cascade-delete widgets referencing it."""
"""Delete a service and cascade-delete widgets + harness data."""
self.init_schema()
with self.connect() as conn:
# The service_id column on dashboard_widgets is added in a later
@@ -1753,6 +1753,15 @@ class SettingsStore:
)
conn.execute("DELETE FROM services WHERE id = ?", (service_id,))
# Cascade-delete harness-managed data (best-effort: the service row is
# already removed; data cleanup must not block service deletion).
try:
from media_library_viewer_api.services.service_data import get_service_data_harness
get_service_data_harness().cascade_delete(service_id)
except Exception:
logger.exception("Failed to cascade-delete harness data for service %s", service_id)
def record_service_task_run(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Append a service task run history row."""
self.init_schema()