test: stabilize full test collection
This commit is contained in:
@@ -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
|
Requirements: Python 3.11+. Cookbook also needs `tmux` for background model
|
||||||
downloads and serves. The app itself is lightweight; local model serving is the
|
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
|
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
|
connect to API or remote model servers instead. Use `--host 0.0.0.0` only when you intentionally want LAN/reverse-proxy access.
|
||||||
you intentionally want LAN/reverse-proxy access.
|
|
||||||
|
|
||||||
### Apple Silicon
|
### Apple Silicon
|
||||||
Docker on macOS cannot use the Metal GPU. For GPU-accelerated Cookbook on an
|
Docker on macOS cannot use the Metal GPU. For GPU-accelerated Cookbook on an
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ calling do_manage_calendar with an rrule stores a single event carrying that RRU
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@@ -14,6 +15,21 @@ from sqlalchemy import create_engine
|
|||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from sqlalchemy.pool import NullPool
|
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
|
import core.database as cdb
|
||||||
from core.database import CalendarEvent
|
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
|
# 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
|
# 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.
|
# 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)
|
monkeypatch.setattr(cdb, "SessionLocal", _TS)
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
from types import SimpleNamespace
|
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
|
from routes import chat_routes
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,12 @@ from core.middleware import require_admin # noqa: E402
|
|||||||
|
|
||||||
# --- token minting: shown once, hashed at rest -----------------------------
|
# --- 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")
|
token_id, raw = P.mint_token("alice")
|
||||||
assert raw.startswith("ody_")
|
assert raw.startswith("ody_")
|
||||||
# The persisted row stores a bcrypt hash + prefix, never the plaintext.
|
# The persisted row stores a bcrypt hash + prefix, never the plaintext.
|
||||||
|
|||||||
@@ -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 statusById = new Map(tasks.map(t => [t.session_id, t]));" in source
|
||||||
assert "const nextStatus = live.status === 'completed'" in source
|
assert "const nextStatus = live.status === 'completed'" in source
|
||||||
assert "? 'done'" 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 "_saveTasks(localTasks);" in source
|
||||||
assert "completedDeps.forEach(t => _refreshDepsAfterInstall(t));" in source
|
assert "completedDeps.forEach(t => _refreshDepsAfterInstall(t));" in source
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ while completing reliably everywhere.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
@@ -21,6 +22,21 @@ from sqlalchemy.orm import sessionmaker
|
|||||||
from sqlalchemy.pool import NullPool
|
from sqlalchemy.pool import NullPool
|
||||||
from unittest.mock import MagicMock
|
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 core.database as cdb
|
||||||
import routes.document_routes as droutes
|
import routes.document_routes as droutes
|
||||||
from core.database import Document
|
from core.database import Document
|
||||||
|
|||||||
@@ -141,4 +141,3 @@ def test_build_anthropic_payload_alternating_roles():
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,23 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
import sys
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
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 core.database import Base, Session, ChatMessage
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@@ -35,4 +52,3 @@ def test_sqlite_foreign_keys_cascade():
|
|||||||
assert db.query(ChatMessage).count() == 0
|
assert db.query(ChatMessage).count() == 0
|
||||||
|
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"""Regression tests for task-result delivery into chat sessions (issue #326)."""
|
"""Regression tests for task-result delivery into chat sessions (issue #326)."""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import sys
|
||||||
import types as _types
|
import types as _types
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -11,6 +12,22 @@ if not isinstance(sqlalchemy, _types.ModuleType):
|
|||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
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 core.database import Base, Session as DbSession
|
||||||
from src.task_scheduler import TaskScheduler
|
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
|
"""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
|
from, so _resolve_defaults returns None. The delivery must still persist a
|
||||||
session instead of crashing on the NOT NULL constraint (issue #326)."""
|
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()
|
db = _make_db()
|
||||||
scheduler = TaskScheduler.__new__(TaskScheduler)
|
scheduler = TaskScheduler.__new__(TaskScheduler)
|
||||||
scheduler._session_manager = None
|
scheduler._session_manager = None
|
||||||
|
|||||||
@@ -1,8 +1,25 @@
|
|||||||
"""Tests for topic keyword matching (src/topic_analyzer.py)."""
|
"""Tests for topic keyword matching (src/topic_analyzer.py)."""
|
||||||
|
import sys
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
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.database import Base, Session as DbSession, ChatMessage as DbChatMessage
|
||||||
from core.session_manager import SessionManager
|
from core.session_manager import SessionManager
|
||||||
from src.topic_analyzer import analyze_topics
|
from src.topic_analyzer import analyze_topics
|
||||||
|
|||||||
@@ -6,6 +6,22 @@ from datetime import datetime
|
|||||||
# from it, so drop the stub here to load the real module under test.
|
# from it, so drop the stub here to load the real module under test.
|
||||||
if "src.database" in sys.modules:
|
if "src.database" in sys.modules:
|
||||||
del sys.modules["src.database"]
|
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
|
import pytest
|
||||||
from src.webhook_manager import validate_webhook_url
|
from src.webhook_manager import validate_webhook_url
|
||||||
|
|||||||
Reference in New Issue
Block a user