You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
motief/tests/conftest.py

124 lines
3.8 KiB

import tempfile
import pytest
import os
from config import config
# Ensure importing database at test-collection time doesn't try to open the real
# application DB. Point the app config to a temporary DB file under the
# system tempdir so the module-level MotionDatabase() in database.py can
# initialize without conflicting with a running instance.
_tmp_dir = tempfile.mkdtemp(prefix="tests_db_")
config.DATABASE_PATH = os.path.join(_tmp_dir, "motions.db")
class FakeEmbedder:
"""Real callable that returns deterministic embeddings. No network calls.
Raises RuntimeError for any call where `fail_indices` are triggered.
fail_indices is the set of positions (0-based) within the texts batch passed
to a single __call__ invocation.
"""
def __init__(self, fail_indices=None, vector_size=8):
self.fail_indices = set(fail_indices or [])
self.vector_size = vector_size
self.call_count = 0
self.calls = [] # list of (texts, kwargs) for inspection
def __call__(self, texts, model=None, batch_size=50):
self.call_count += 1
self.calls.append((list(texts), {"model": model, "batch_size": batch_size}))
results = []
for i, text in enumerate(texts):
if i in self.fail_indices:
raise RuntimeError(
f"Simulated embedding failure for index {i}: {text!r}"
)
results.append([0.1 * (i + 1)] * self.vector_size)
return results
@pytest.fixture
def mem_db(tmp_path):
"""In-memory MotionDatabase with full schema. No filesystem side effects.
MotionDatabase(':memory:') may raise when os.path.dirname(':memory:') is
empty. Try in-memory first, fall back to a tmp file if that fails.
"""
from database import (
MotionDatabase,
) # lazy import — database module not imported at module level
try:
db = MotionDatabase(":memory:")
except Exception:
db = MotionDatabase(str(tmp_path / "test.db"))
yield db
@pytest.fixture
def fake_embedder():
"""FakeEmbedder with no failures by default."""
return FakeEmbedder()
# Load test fixtures from the utils package so pytest can discover them.
pytest_plugins = ["tests.utils.migration_fixtures"]
@pytest.fixture
def tmp_duckdb_path(tmp_path):
p = tmp_path / "test.db"
return str(p)
@pytest.fixture
def tmp_duckdb_conn(tmp_duckdb_path):
# Import duckdb lazily so running pytest doesn't fail on machines
# where duckdb is not installed (CI / contributor machines that don't
# need the duckdb-based fixtures). If duckdb is missing, skip this
# fixture at runtime when it's requested.
try:
import duckdb
except Exception:
pytest.skip("duckdb not installed, skipping duckdb fixtures")
conn = duckdb.connect(database=tmp_duckdb_path)
yield conn
try:
conn.close()
except Exception:
pass
@pytest.fixture
def monkeypatch_ai_provider(monkeypatch):
"""Patch ai_provider.get_embedding to return deterministic 16-dim vector."""
import ai_provider
fake = [0.01] * 16
monkeypatch.setattr(ai_provider, "get_embedding", lambda text, model=None: fake)
return fake
@pytest.fixture
def mock_odata_client(monkeypatch):
"""
Patch requests.Session.get for OData calls.
Returns a configurable mock — set mock_odata_client.response to override.
"""
import requests
from unittest.mock import MagicMock
mock_response = MagicMock()
mock_response.raise_for_status.return_value = None
mock_response.json.return_value = {"value": []}
class MockSession:
response = mock_response
def get(self, *args, **kwargs):
return self.response
monkeypatch.setattr(requests, "Session", MockSession)
return mock_response