From 5869106089e5580c5501fe21df7ca9984c281d3b Mon Sep 17 00:00:00 2001 From: Vykos Date: Thu, 4 Jun 2026 01:27:29 +0200 Subject: [PATCH] test: stabilize full test collection --- README.md | 3 +-- tests/test_calendar_rrule.py | 20 ++++++++++++++++ tests/test_chat_image_routing.py | 7 ++++++ tests/test_companion_pairing.py | 7 +++++- ...okbook_dependency_completion_regression.py | 3 ++- ...test_document_close_clears_active_route.py | 16 +++++++++++++ tests/test_llm_core_sanitize_tool_calls.py | 1 - tests/test_sqlite_foreign_keys.py | 18 +++++++++++++- tests/test_task_scheduler_session_delivery.py | 24 ++++++++++++++++++- tests/test_topic_analyzer.py | 17 +++++++++++++ tests/test_webhook_ssrf_resilience.py | 16 +++++++++++++ 11 files changed, 125 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5e7d3d8..4fd7f48 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,7 @@ python -m uvicorn app:app --host 127.0.0.1 --port 7000 Requirements: Python 3.11+. Cookbook also needs `tmux` for background model downloads and serves. The app itself is lightweight; local model serving is the heavy part and depends on the model, runtime, GPU, and VRAM, so small hosts can -connect to API or remote model servers instead. Use `--host 0.0.0.0` only when -you intentionally want LAN/reverse-proxy access. +connect to API or remote model servers instead. Use `--host 0.0.0.0` only when you intentionally want LAN/reverse-proxy access. ### Apple Silicon Docker on macOS cannot use the Metal GPU. For GPU-accelerated Cookbook on an diff --git a/tests/test_calendar_rrule.py b/tests/test_calendar_rrule.py index 40047b7..c49f142 100644 --- a/tests/test_calendar_rrule.py +++ b/tests/test_calendar_rrule.py @@ -6,6 +6,7 @@ calling do_manage_calendar with an rrule stores a single event carrying that RRU """ import json +import sys import tempfile import uuid @@ -14,6 +15,21 @@ from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.pool import NullPool + +def _drop_fake_core_database(): + parent = sys.modules.get("core") + attr = getattr(parent, "database", None) if parent is not None else None + mod = sys.modules.get("core.database") or attr + if mod is None or isinstance(getattr(mod, "__file__", None), str): + return + sys.modules.pop("core.database", None) + sys.modules.pop("src.database", None) + if parent is not None and attr is mod: + delattr(parent, "database") + + +_drop_fake_core_database() + import core.database as cdb from core.database import CalendarEvent @@ -32,6 +48,10 @@ def _bind_temp_db(monkeypatch): # do_manage_calendar does `from core.database import SessionLocal` at call # time, so patch the module attribute to our temp DB — via monkeypatch so it # is RESTORED after each test and can't leak into later tests in the process. + monkeypatch.setitem(sys.modules, "core.database", cdb) + parent = sys.modules.get("core") + if parent is not None: + monkeypatch.setattr(parent, "database", cdb, raising=False) monkeypatch.setattr(cdb, "SessionLocal", _TS) yield diff --git a/tests/test_chat_image_routing.py b/tests/test_chat_image_routing.py index dc2a869..92b8476 100644 --- a/tests/test_chat_image_routing.py +++ b/tests/test_chat_image_routing.py @@ -1,6 +1,13 @@ import json +import sys from types import SimpleNamespace +_endpoint_resolver = sys.modules.get("src.endpoint_resolver") +if _endpoint_resolver is not None and not getattr(_endpoint_resolver, "__file__", None): + sys.modules.pop("src.endpoint_resolver", None) + sys.modules.pop("routes.model_routes", None) + sys.modules.pop("routes.chat_routes", None) + from routes import chat_routes diff --git a/tests/test_companion_pairing.py b/tests/test_companion_pairing.py index b8d987b..c4abcd5 100644 --- a/tests/test_companion_pairing.py +++ b/tests/test_companion_pairing.py @@ -78,7 +78,12 @@ from core.middleware import require_admin # noqa: E402 # --- token minting: shown once, hashed at rest ----------------------------- -def test_mint_token_returns_raw_once_and_stores_only_a_hash(): +def test_mint_token_returns_raw_once_and_stores_only_a_hash(monkeypatch): + monkeypatch.setitem(sys.modules, "core.database", _db) + parent = sys.modules.get("core") + if parent is not None: + monkeypatch.setattr(parent, "database", _db, raising=False) + token_id, raw = P.mint_token("alice") assert raw.startswith("ody_") # The persisted row stores a bcrypt hash + prefix, never the plaintext. diff --git a/tests/test_cookbook_dependency_completion_regression.py b/tests/test_cookbook_dependency_completion_regression.py index 642611e..0ff90fd 100644 --- a/tests/test_cookbook_dependency_completion_regression.py +++ b/tests/test_cookbook_dependency_completion_regression.py @@ -22,7 +22,8 @@ def test_background_status_poll_reconciles_into_local_tasks(): assert "const statusById = new Map(tasks.map(t => [t.session_id, t]));" in source assert "const nextStatus = live.status === 'completed'" in source assert "? 'done'" in source - assert "live.status === 'error'" in source + assert ": (live.status === 'error'" in source + assert "? 'error'" in source assert "_saveTasks(localTasks);" in source assert "completedDeps.forEach(t => _refreshDepsAfterInstall(t));" in source diff --git a/tests/test_document_close_clears_active_route.py b/tests/test_document_close_clears_active_route.py index b1ab9c7..5428d4f 100644 --- a/tests/test_document_close_clears_active_route.py +++ b/tests/test_document_close_clears_active_route.py @@ -13,6 +13,7 @@ while completing reliably everywhere. """ import tempfile +import sys import uuid from types import SimpleNamespace @@ -21,6 +22,21 @@ from sqlalchemy.orm import sessionmaker from sqlalchemy.pool import NullPool from unittest.mock import MagicMock + +def _drop_fake_core_database(): + parent = sys.modules.get("core") + attr = getattr(parent, "database", None) if parent is not None else None + mod = sys.modules.get("core.database") or attr + if mod is None or isinstance(getattr(mod, "__file__", None), str): + return + sys.modules.pop("core.database", None) + sys.modules.pop("src.database", None) + if parent is not None and attr is mod: + delattr(parent, "database") + + +_drop_fake_core_database() + import core.database as cdb import routes.document_routes as droutes from core.database import Document diff --git a/tests/test_llm_core_sanitize_tool_calls.py b/tests/test_llm_core_sanitize_tool_calls.py index 7ff319b..7469099 100644 --- a/tests/test_llm_core_sanitize_tool_calls.py +++ b/tests/test_llm_core_sanitize_tool_calls.py @@ -141,4 +141,3 @@ def test_build_anthropic_payload_alternating_roles(): - diff --git a/tests/test_sqlite_foreign_keys.py b/tests/test_sqlite_foreign_keys.py index c3df88c..dcf5642 100644 --- a/tests/test_sqlite_foreign_keys.py +++ b/tests/test_sqlite_foreign_keys.py @@ -1,6 +1,23 @@ import pytest +import sys from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker + + +def _drop_fake_core_database(): + parent = sys.modules.get("core") + attr = getattr(parent, "database", None) if parent is not None else None + mod = sys.modules.get("core.database") or attr + if mod is None or isinstance(getattr(mod, "__file__", None), str): + return + sys.modules.pop("core.database", None) + sys.modules.pop("src.database", None) + if parent is not None and attr is mod: + delattr(parent, "database") + + +_drop_fake_core_database() + from core.database import Base, Session, ChatMessage from datetime import datetime @@ -35,4 +52,3 @@ def test_sqlite_foreign_keys_cascade(): assert db.query(ChatMessage).count() == 0 db.close() - diff --git a/tests/test_task_scheduler_session_delivery.py b/tests/test_task_scheduler_session_delivery.py index cafff71..4f35cb3 100644 --- a/tests/test_task_scheduler_session_delivery.py +++ b/tests/test_task_scheduler_session_delivery.py @@ -1,5 +1,6 @@ """Regression tests for task-result delivery into chat sessions (issue #326).""" import asyncio +import sys import types as _types import pytest @@ -11,6 +12,22 @@ if not isinstance(sqlalchemy, _types.ModuleType): from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker + +def _drop_fake_core_database(): + parent = sys.modules.get("core") + attr = getattr(parent, "database", None) if parent is not None else None + mod = sys.modules.get("core.database") or attr + if mod is None or isinstance(getattr(mod, "__file__", None), str): + return + sys.modules.pop("core.database", None) + sys.modules.pop("src.database", None) + if parent is not None and attr is mod: + delattr(parent, "database") + + +_drop_fake_core_database() + +import core.database as cdb from core.database import Base, Session as DbSession from src.task_scheduler import TaskScheduler @@ -46,10 +63,15 @@ def _make_task(): ) -def test_session_delivery_survives_empty_database(): +def test_session_delivery_survives_empty_database(monkeypatch): """On a fresh/wiped database there is no session to inherit endpoint/model from, so _resolve_defaults returns None. The delivery must still persist a session instead of crashing on the NOT NULL constraint (issue #326).""" + monkeypatch.setitem(sys.modules, "core.database", cdb) + parent = sys.modules.get("core") + if parent is not None: + monkeypatch.setattr(parent, "database", cdb, raising=False) + db = _make_db() scheduler = TaskScheduler.__new__(TaskScheduler) scheduler._session_manager = None diff --git a/tests/test_topic_analyzer.py b/tests/test_topic_analyzer.py index 6101526..c47d14e 100644 --- a/tests/test_topic_analyzer.py +++ b/tests/test_topic_analyzer.py @@ -1,8 +1,25 @@ """Tests for topic keyword matching (src/topic_analyzer.py).""" +import sys from types import SimpleNamespace import pytest from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker + + +def _drop_fake_core_database(): + parent = sys.modules.get("core") + attr = getattr(parent, "database", None) if parent is not None else None + mod = sys.modules.get("core.database") or attr + if mod is None or isinstance(getattr(mod, "__file__", None), str): + return + sys.modules.pop("core.database", None) + sys.modules.pop("src.database", None) + if parent is not None and attr is mod: + delattr(parent, "database") + + +_drop_fake_core_database() + from core.database import Base, Session as DbSession, ChatMessage as DbChatMessage from core.session_manager import SessionManager from src.topic_analyzer import analyze_topics diff --git a/tests/test_webhook_ssrf_resilience.py b/tests/test_webhook_ssrf_resilience.py index 6cc7312..c7f93b9 100644 --- a/tests/test_webhook_ssrf_resilience.py +++ b/tests/test_webhook_ssrf_resilience.py @@ -6,6 +6,22 @@ from datetime import datetime # from it, so drop the stub here to load the real module under test. if "src.database" in sys.modules: del sys.modules["src.database"] +_core_database = sys.modules.get("core.database") +_core_database_all = getattr(_core_database, "__all__", None) if _core_database is not None else None +if ( + _core_database is not None + and ( + not getattr(_core_database, "__file__", None) + or ( + _core_database_all is not None + and ( + not isinstance(_core_database_all, (list, tuple, set)) + or not all(isinstance(name, str) for name in _core_database_all) + ) + ) + ) +): + del sys.modules["core.database"] import pytest from src.webhook_manager import validate_webhook_url