diff --git a/shelfmark/main.py b/shelfmark/main.py index f8076627..6d3e3ea4 100644 --- a/shelfmark/main.py +++ b/shelfmark/main.py @@ -285,7 +285,7 @@ if user_db is not None and download_history_service is not None: _reconciled["marked"], _reconciled["reopened"], ) - except (ImportError, OSError, RuntimeError, ValueError, sqlite3.Error) as exc: + except _IMPORT_OPERATIONAL_ERRORS as exc: logger.warning("Could not reconcile interrupted downloads at startup: %s", exc) # Start download coordinator diff --git a/tests/core/test_startup_reconcile_guard.py b/tests/core/test_startup_reconcile_guard.py new file mode 100644 index 00000000..96efa680 --- /dev/null +++ b/tests/core/test_startup_reconcile_guard.py @@ -0,0 +1,39 @@ +"""A reconcile that cannot run must never stop the app from starting.""" + +from __future__ import annotations + +import importlib +from unittest.mock import patch + +import pytest + +from shelfmark.core.logger import CustomLogger + + +@pytest.mark.parametrize("error", [TypeError("unexpected row shape"), OSError("disk went away")]) +def test_a_failing_startup_reconcile_only_logs_a_warning(error): + """The module-level guard in main.py runs before the coordinator starts. + + An error it does not catch fails the import of shelfmark.main, and the worker then + fails to boot on every restart. + """ + # Patch the class, not main.logger: reloading main builds a new logger object. + with ( + patch("shelfmark.download.orchestrator.start"), + patch("shelfmark.download.warmup.start"), + patch( + "shelfmark.core.startup_reconcile.reconcile_interrupted_downloads", + side_effect=error, + ) as reconcile, + patch.object(CustomLogger, "warning") as warning, + ): + import shelfmark.main as main + + importlib.reload(main) + + # Importing and then reloading runs the module body twice, so only check it was reached. + assert reconcile.called + assert any( + "Could not reconcile interrupted downloads at startup" in str(call.args[0]) + for call in warning.call_args_list + )