"""Pytest 配置和共享 fixtures."""
import pytest
import os
import sys
import tempfile
from pathlib import Path
from unittest.mock import MagicMock
# ============================================================
# 关键:创建全局 mock 对象
# ============================================================
_mock_driver = MagicMock()
_mock_session = MagicMock()
_mock_driver.session.return_value.__enter__.return_value = _mock_session
_mock_driver.session.return_value.__exit__.return_value = None
# 创建一个 Mock GraphDatabase 类
class MockGraphDatabase:
@staticmethod
def driver(*args, **kwargs):
return _mock_driver
def _install_neo4j_mock():
"""安装 Neo4j mock,阻止所有真实数据库连接."""
import neo4j
neo4j.GraphDatabase = MockGraphDatabase
# 如果 src.graphiti_client 已经被导入,也替换它
if 'src.graphiti_client' in sys.modules:
sys.modules['src.graphiti_client'].GraphDatabase = MockGraphDatabase
# 立即安装 mock(在 pytest 收集测试之前)
_install_neo4j_mock()
def pytest_configure(config):
"""pytest 配置 hook - 在测试收集之前执行."""
_install_neo4j_mock()
def pytest_collection_modifyitems(session, config, items):
"""在测试收集完成后,再次确保 mock 已安装."""
_install_neo4j_mock()
# 确保 src.graphiti_client.GraphDatabase 被替换
if 'src.graphiti_client' in sys.modules:
sys.modules['src.graphiti_client'].GraphDatabase = MockGraphDatabase
# 导入 src 模块
from src.config_manager import ConfigManager
from src.graphiti_client import GraphitiClient
from src.cache_manager import get_cache_manager
# 再次确保替换(导入后)
import src.graphiti_client
src.graphiti_client.GraphDatabase = MockGraphDatabase
# 提供 fixture 以便测试可以访问 mock 对象
@pytest.fixture(scope="session")
def mock_neo4j_driver_global():
"""全局 mock Neo4j 驱动."""
return _mock_driver, _mock_session
@pytest.fixture(autouse=True)
def mock_neo4j_driver(mock_neo4j_driver_global):
"""每个测试都使用全局 mock."""
# 每次测试前确保 mock 仍然生效
import src.graphiti_client
src.graphiti_client.GraphDatabase = MockGraphDatabase
return mock_neo4j_driver_global
@pytest.fixture(scope="session")
def _session_temp_dir():
"""创建会话级别的临时目录."""
with tempfile.TemporaryDirectory() as tmpdir:
yield Path(tmpdir)
@pytest.fixture(autouse=True)
def isolate_config_path(_session_temp_dir, monkeypatch):
"""隔离配置文件路径,防止测试修改用户真实配置."""
# 创建 .graphitiace 目录
config_dir = _session_temp_dir / ".graphitiace"
config_dir.mkdir(parents=True, exist_ok=True)
# 在 Windows 上,Path.home() 使用 USERPROFILE 环境变量
# 在 Unix 上,使用 HOME 环境变量
monkeypatch.setenv("USERPROFILE", str(_session_temp_dir))
monkeypatch.setenv("HOME", str(_session_temp_dir))
# 同时 patch Path.home() 以确保完全隔离
monkeypatch.setattr(Path, "home", staticmethod(lambda: _session_temp_dir))
yield _session_temp_dir
@pytest.fixture
def temp_config_dir(_session_temp_dir):
"""提供临时配置目录(向后兼容)."""
return _session_temp_dir
@pytest.fixture
def config_manager(temp_config_dir):
"""创建配置管理器实例."""
return ConfigManager(config_path=temp_config_dir / ".graphitiace" / "config.json")
@pytest.fixture
def graphiti_client(config_manager):
"""创建 Graphiti 客户端实例(不连接数据库)."""
return GraphitiClient(config_manager)
@pytest.fixture
def cache_manager():
"""获取缓存管理器实例."""
cache = get_cache_manager()
cache.clear() # 清理缓存
yield cache
cache.clear() # 测试后清理
@pytest.fixture
def mock_neo4j_config():
"""模拟 Neo4j 配置."""
return {
"uri": "bolt://localhost:7687",
"username": "neo4j",
# 这里的密码需要与本地 Docker Neo4j 实例保持一致,避免在测试时产生认证失败日志
"password": "password",
"database": "neo4j"
}
@pytest.fixture
def mock_api_config():
"""模拟 API 配置."""
return {
"provider": "openai",
"api_key": "test_api_key",
"model": "gpt-4"
}
@pytest.fixture
def mock_driver():
"""创建模拟的 Neo4j 驱动."""
from unittest.mock import MagicMock
driver = MagicMock()
session = MagicMock()
driver.session.return_value.__enter__.return_value = session
driver.session.return_value.__exit__.return_value = None
return driver, session