From 7efdd1d7ba190fe45f3b21bfc8e06c2c329dcd20 Mon Sep 17 00:00:00 2001 From: Simon Pinfold Date: Tue, 1 Sep 2026 15:27:54 -0700 Subject: [PATCH] fix(assets): disable asset routes when database dependencies are unavailable Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- app/assets/lifecycle.py | 11 ++- main.py | 4 +- .../services/test_deps_unavailable_gate.py | 75 +++++++++++++++++++ 3 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 tests-unit/assets_test/services/test_deps_unavailable_gate.py diff --git a/app/assets/lifecycle.py b/app/assets/lifecycle.py index e94041c5e..977cd19d9 100644 --- a/app/assets/lifecycle.py +++ b/app/assets/lifecycle.py @@ -12,7 +12,7 @@ from app.assets.database.queries.records import delete_record from app.assets.helpers import sql_path_under_prefix from app.assets.services.hash_mode_state import enqueue_transition_work from app.assets.services.hash_mode_state import record_transition_intent -from app.database.db import can_create_session, create_session, init_db +from app.database.db import can_create_session, create_session, dependencies_available, init_db from comfy.cli_args import args _excluded_scan_roots: set[str] = set() @@ -37,6 +37,15 @@ def enqueue_mode_transition_work() -> None: session.commit() +def assets_dependencies_ready() -> bool: + from app.assets.api.routes import disable_assets_routes + + if dependencies_available(): + return True + disable_assets_routes() + return False + + def init_db_and_state() -> None: init_db() record_hash_mode_transition_intent() diff --git a/main.py b/main.py index b40ec00d1..44a11251f 100644 --- a/main.py +++ b/main.py @@ -23,7 +23,7 @@ file_log_outputs = get_file_log_outputs(args.verbose) setup_logger(log_level=console_log_level, file_outputs=file_log_outputs, use_stdout=args.log_stdout) from app.assets import mode -from app.assets.lifecycle import cleanup_temp_filesystem, init_db_and_state, run_shutdown, run_startup +from app.assets.lifecycle import assets_dependencies_ready, cleanup_temp_filesystem, init_db_and_state, run_shutdown, run_startup from app.assets.seeder import asset_seeder import itertools import utils.extra_config @@ -479,7 +479,7 @@ def hijack_progress(server_instance): def setup_database(): - if not dependencies_available(): + if not assets_dependencies_ready(): return try: diff --git a/tests-unit/assets_test/services/test_deps_unavailable_gate.py b/tests-unit/assets_test/services/test_deps_unavailable_gate.py new file mode 100644 index 000000000..5c66da596 --- /dev/null +++ b/tests-unit/assets_test/services/test_deps_unavailable_gate.py @@ -0,0 +1,75 @@ +import json + +import pytest + +from app.assets import lifecycle, mode +from app.assets.api import routes + +_VALID_HASH = "blake3:" + "a" * 64 + + +class _JsonRequest: + def __init__(self, payload: dict) -> None: + self.match_info: dict[str, str] = {} + self._payload = payload + + async def json(self) -> dict: + return self._payload + + +@pytest.fixture +def registered_but_uninitialised(monkeypatch): + monkeypatch.setattr(routes, "_ASSETS_ENABLED", True) + monkeypatch.setattr(mode, "_args", None) + + +@pytest.mark.asyncio +async def test_uninitialised_mode_serves_the_disabled_envelope_not_a_crash( + registered_but_uninitialised, +): + routes.disable_assets_routes() + + response = await routes.create_asset_from_hash_route( + _JsonRequest({"hash": _VALID_HASH, "name": "x.bin"}) + ) + + assert response.status == 503 + body = json.loads(response.body) + assert body["error"]["code"] == "SERVICE_DISABLED", ( + "with its database dependencies missing the asset system is disabled, which is a " + "known state the envelope already describes — not an unexpected server error" + ) + + +@pytest.mark.asyncio +async def test_route_crashes_while_enabled_with_mode_uninitialised( + registered_but_uninitialised, +): + with pytest.raises(RuntimeError, match="was not called before hashing_enabled"): + await routes.create_asset_from_hash_route( + _JsonRequest({"hash": _VALID_HASH, "name": "x.bin"}) + ) + + +def test_missing_dependencies_disable_the_asset_routes(monkeypatch): + monkeypatch.setattr(lifecycle, "dependencies_available", lambda: False) + calls: list[int] = [] + monkeypatch.setattr(routes, "disable_assets_routes", lambda: calls.append(1)) + + ready = lifecycle.assets_dependencies_ready() + + assert ready is False + assert calls == [1], ( + "the deps-unavailable branch must actually disable the routes, exactly once" + ) + + +def test_available_dependencies_leave_the_routes_alone(monkeypatch): + monkeypatch.setattr(lifecycle, "dependencies_available", lambda: True) + calls: list[int] = [] + monkeypatch.setattr(routes, "disable_assets_routes", lambda: calls.append(1)) + + ready = lifecycle.assets_dependencies_ready() + + assert ready is True + assert calls == []