diff --git a/routes/admin_observability_routes.py b/routes/admin_observability_routes.py index b1cfe77..f4b9db6 100644 --- a/routes/admin_observability_routes.py +++ b/routes/admin_observability_routes.py @@ -2321,6 +2321,14 @@ def ppt_audit_history(): def host_health_dashboard(): """三主機 Ollama + 4 個 MCP server 即時健康(同時寫入 host_health_probes 留歷史)""" import time as _time + + def _session_uses_sqlite(session) -> bool: + try: + bind = session.get_bind() + return getattr(getattr(bind, 'dialect', None), 'name', None) == 'sqlite' + except Exception: + return False + ollama_hosts = [] probe_records = [] # 收集本次 probe 結果以批次寫 DB try: @@ -2365,19 +2373,22 @@ def host_health_dashboard(): try: _session = get_session() try: - for rec in probe_records: - _session.execute( - sa_text(""" - INSERT INTO host_health_probes - (host_label, host_url, healthy, unhealthy_mark, - models_count, response_ms, error_msg) - VALUES - (:host_label, :host_url, :healthy, :unhealthy_mark, - :models_count, :response_ms, :error_msg) - """), - rec, - ) - _session.commit() + if _session_uses_sqlite(_session): + logger.debug("Skipping host health probe persistence on SQLite") + else: + for rec in probe_records: + _session.execute( + sa_text(""" + INSERT INTO host_health_probes + (host_label, host_url, healthy, unhealthy_mark, + models_count, response_ms, error_msg) + VALUES + (:host_label, :host_url, :healthy, :unhealthy_mark, + :models_count, :response_ms, :error_msg) + """), + rec, + ) + _session.commit() finally: _session.close() except Exception: diff --git a/tests/test_admin_observability_routes.py b/tests/test_admin_observability_routes.py index 2f2d43b..89e53ff 100644 --- a/tests/test_admin_observability_routes.py +++ b/tests/test_admin_observability_routes.py @@ -194,6 +194,42 @@ def test_host_health_200(client, monkeypatch): assert r.status_code == 200 +def test_host_health_skips_probe_persistence_on_sqlite(client, monkeypatch): + """Local visual QA uses SQLite; GET page should not warn on BIGINT autoincrement drift.""" + from routes import admin_observability_routes as mod + import services.ollama_service as ollama_mod + import requests as _r + + class _Dialect: + name = 'sqlite' + + class _Bind: + dialect = _Dialect() + + session = _fake_session([]) + session.get_bind.return_value = _Bind() + monkeypatch.setattr(mod, 'get_session', lambda: session) + monkeypatch.setattr(ollama_mod, '_is_unhealthy', lambda _h: False, raising=False) + monkeypatch.setattr(ollama_mod, '_unhealthy_marks', {}, raising=False) + + class FakeResponse: + status_code = 200 + + def json(self): + return {'models': [{'name': 'bge-m3'}]} + + monkeypatch.setattr(_r, 'get', lambda *_a, **_kw: FakeResponse()) + + r = client.get('/observability/host_health') + + assert r.status_code == 200 + insert_calls = [ + call for call in session.execute.call_args_list + if 'INSERT INTO host_health_probes' in str(call.args[0]) + ] + assert insert_calls == [] + + # ────────────────────────────────────────────────────────────────────────── # Phase 33 Auth Hardening — 未登入必 302 redirect 到 /login # ──────────────────────────────────────────────────────────────────────────