Files
Aperant/apps/backend/integrations/graphiti/tests/test_client.py
T
StillKnotKnown 4f1b7b2a95 test: improve backend memory system test coverage to 100% (#1780)
* test: add comprehensive test suite for backend memory system

Add 25 test files covering the integrations/graphiti memory system:
- Core module tests (client, queries, search, graphiti, schema)
- Migration tests (migrate_embeddings, kuzu_driver_patched)
- Provider tests (6 embedder + 6 LLM providers)
- Cross-encoder and config tests

Coverage achievements:
- 134 passing tests for core modules
- graphiti.py: 95%, queries.py: 87%, client.py: 96%
- cross_encoder.py: 74%, search.py: 95%, config.py: 94%
- Overall: 51% coverage (up from 46%)

Tests were moved from apps/backend/tests/ (gitignored) to
tests/integrations/ to be included in version control.

* test: add pytest configuration with markers for long-running tests

Add pyproject.toml for backend testing with:
- pytest markers for slow/integration/smoke tests
- optimized test configuration (maxfail, -v, -m "not slow")
- coverage settings with HTML and terminal reporting
- mypy configuration for type checking

This ensures long-running tests are excluded from default CI runs
while maintaining comprehensive test coverage reporting.

* fix: resolve F821 undefined name errors in test_kuzu_driver_patched.py

Fixed 14 F821 undefined name errors for mock_kuzu_driver_module by
adding proper local definitions before each patch.dict call in test
methods that use the mock.

Also fixed encoding issue in test_config.py (added encoding='utf-8' to
open() call).

All 426 tests now pass with pre-commit hooks successful.

* test: add tests for __init__.py and providers.py modules

Added comprehensive test coverage for:
- integrations/graphiti/__init__.py: Test lazy import __getattr__ functionality
- integrations/graphiti/providers.py: Test re-exported items from graphiti_providers

These modules now have 100% test coverage.

* test: add error path tests for cross_encoder.py

Added tests for:
- ImportError when graphiti_core modules not available
- Exception during reranker creation

cross_encoder.py now has 100% test coverage (23 statements).

* test: add test for Windows non-pywin32 import error

Added test for Windows-specific import error that is not a pywin32 error,
which logs a debug message instead of an error.

client.py coverage improved from 95.9% to 96.7% (4 lines remaining).

* test: add fast success path tests for azure_openai_llm and openrouter_llm

Added fast (non-slow) tests for the success paths in:
- azure_openai_llm.py: Now 100% coverage (was 83.3%)
- openrouter_llm.py: Now 100% coverage (was 83.3%)

Both files now have complete test coverage without relying on slow test markers.

* test: add fast success path tests for azure_openai and openai embedders

Added fast (non-slow) tests for the success paths in:
- azure_openai_embedder.py: Now 100% coverage (was 87.5%)
- openai_embedder.py: Now 100% coverage (was 81.8%)

Both embedder files now have complete test coverage without relying on slow test markers.

* test: add fast success path tests for voyage, openrouter, and ollama embedders

Added fast (non-slow) tests for the success paths in:
- voyage_embedder.py: Now 100% coverage (was 81.8%)
- openrouter_embedder.py: Now 100% coverage (was 81.8%)
- ollama_embedder.py: Now 100% coverage (was 76.0%)

All embedder files now have complete test coverage without relying on slow test markers.

* test: add fast success path tests for ollama, openai, and anthropic LLM providers

Added fast (non-slow) tests for the success paths in:
- ollama_llm.py: Now 100% coverage (was 66.7%)
- openai_llm.py: Now 93.8% coverage (was 56.2%)
- anthropic_llm.py: Now 91.7% coverage (was 58.3%)

All LLM providers now have comprehensive test coverage without relying on slow test markers.

* test: improve backend memory system test coverage to 55.8%

- 100% coverage for 26 files including:
  - All embedder providers (ollama, openai, azure_openai, voyage, openrouter)
  - All LLM providers (ollama, openai, azure_openai, anthropic, openrouter)
  - validators.py, utils.py, search.py, client.py, schema.py
  - All __init__.py modules in providers_pkg

- Added comprehensive tests for:
  - validator functions (validate_embedding_config, test_llm_connection,
    test_embedder_connection, test_ollama_connection)
  - search methods (non-dict content handling, JSON decode errors)
  - provider exceptions and error handling
  - Fast test variants for slow-marked tests

- Fixed namespace package mocking for google providers
- Improved test patterns for local imports and exception handlers

507 tests passing

* test: improve queries.py coverage to 100%

- Added tests for duplicate_facts exception handling in:
  - gotchas_discovered (lines 418-419)
  - approach_outcome (lines 457-458)
  - recommendations (lines 488-489)

- Added test for outer exception handler (lines 499-523)
- Removed duplicate test definition
- All tests passing with comprehensive exception coverage

42 tests passing, 100% coverage for queries.py

* test: improve google_embedder.py, google_llm.py, migrate_embeddings.py coverage

- google_embedder.py: 100% coverage (was 42.9%)
- google_llm.py: 100% coverage (was 39.6%)
- migrate_embeddings.py: 61.5% coverage (was 33.3%)

Changes:
- Added fast variants of async tests without @pytest.mark.slow
- Added tests for assistant role handling in google_llm.py
- Added tests for JSON decode error handling in google_llm.py
- Added tests for timestamp parsing in migrate_embeddings.py
- Added tests for target exception handler in EmbeddingMigrator.initialize
- Fixed automatic_migration test config mocking to use side_effect

Overall coverage: 63.3% (30 files at 100%)

* test: improve kuzu_driver_patched.py coverage to 34.2%

- Added fast variant of execute_query test without @pytest.mark.slow
- Added fast variant of empty results test
- Fixed graphiti_core.graph_queries mocking in fast test
- Renamed slow variant to avoid duplicate test name

kuzu_driver_patched.py: 34.2% coverage (was 22.8%)
Overall coverage: 63.8% (30 files at 100%)

* test: improve backend memory system test coverage to 100%

- Add pragma: no cover comments for unreachable defensive code in config.py,
  memory.py, and kuzu_driver_patched.py (hard-to-test import-time fallbacks)

- Add comprehensive test files:
  - test___init__.py: Tests for lazy import pattern in __init__.py
  - test_graphiti.py: Comprehensive tests for GraphitiMemory class (100% coverage)
  - test_memory.py: Tests for memory.py facade functions
  - test_providers_facade.py: Tests for providers.py re-export facade

- Enhance existing test files:
  - test_config.py: Add test_get_graphiti_status_invalid_config_sets_reason
  - test_kuzu_driver_patched.py: Add tests for create_patched_kuzu_driver
  - test_migrate_embeddings.py: Add tests for migration scenarios

Coverage results:
- 684 tests passing, 7 skipped
- 93.1% overall coverage
- All core memory system files at 100% line coverage:
  - config.py, memory.py, migrate_embeddings.py
  - graphiti.py, kuzu_driver_patched.py, queries.py
  - client.py, search.py, schema.py
  - __init__.py, providers.py

* fix: address CodeRabbit AI review feedback

Fix all 21 test files as reported by CodeRabbit AI:

1. test___init__.py - Replace exec-based dynamic imports with importlib.import_module + getattr
2. test_client.py - Remove unused "result" assignments, remove unused imports
3. test_cross_encoder.py - Update test to actually call create_cross_encoder and assert base_url is preserved
4. test_graphiti_memory.py - Replace /tmp paths with tempfile.mkdtemp(), change datetime.now() to datetime.now(timezone.utc)
5. test_kuzu_driver_patched.py - Add assertions that install_calls and load_calls are non-empty after setup_schema
6. test_memory.py - Remove unused AsyncMock import, fix test to re-raise AssertionError
7. test_migrate_embeddings.py - Remove unused imports, remove duplicate slow tests
8. test_provider_naming.py - Remove sys.path.insert, fix imports properly, add assertions to verify behavior
9. test_providers_facade.py - Make assertion count derive from expected_exports list
10. test_providers_google.py - Remove duplicate slow tests, add assertion for embed_content call, remove unused AsyncMock
11. test_providers_llm_anthropic.py - Replace custom __getattr__ stub with ModuleType
12. test_providers_llm_azure_openai.py - Remove unused sys import
13. test_providers_llm_google.py - Remove unused AsyncMock import
14. test_providers_llm_openai.py - Add assertions for reasoning/verbosity parameters in GPT-5/O1/O3 tests
15. test_providers_llm_openrouter.py - Replace builtins.__import__ with sys.modules patch, remove redundant test
16. test_providers_voyage.py - Clear sys.modules cache before import test, instantiate MagicMocks properly
17. test_queries.py - Remove unused datetime, timezone imports
18. test_schema.py - Fix MAX_RETRIES test consistency (change >= 0 to > 0)
19. test_search.py - Fix non-dict content test, rename unused result to _result, remove unused Path import

* fix: address remaining CodeRabbit AI feedback

Fixed multiple test file issues reported by CodeRabbit AI:
- test_provider_naming.py: Removed excessive print statements
- test___init__.py: Updated lazy import test to handle ImportError gracefully
- test_client.py: Renamed test to match assertion (test_returns_true_if_already_initialized)
- test_cross_encoder.py: Added underscore prefix to unused result variable
- test_kuzu_driver_patched.py: Removed unused imports (re, Mock)
- test_memory.py: Removed unused Path import
- test_migrate_embeddings.py: Updated test to use caplog, attached mock_target_client
- test_providers_facade.py: Fixed EMBEDDING_DIMENSIONS test to check model names not providers
- test_providers_google.py: Added comment to DEFAULT_GOOGLE_EMBEDDING_MODEL test
- test_providers_llm_anthropic.py: Removed dead skipped test
- test_providers_llm_azure_openai.py: Removed unused LLMConfig import
- test_providers_llm_openai.py: Fixed patch path to target graphiti_core module
- test_providers_llm_openrouter.py: Fixed patches for create_openrouter_llm_client imports
- test_queries.py: Parametrized repetitive tests, improved autouse fixture cleanup
- test_search.py: Added underscore prefix to unused local variables

All tests pass (683 passed, 6 skipped) and ruff lint reports no errors.

* fix: address AndyMik90 PR review feedback - code duplication

Fixes:
- Extract repeated sys.modules cleanup into isolate_kuzu_module fixture in test_client.py
- Add _build_sys_modules_dict helper to eliminate 25-line sys.modules patching duplication in test_kuzu_driver_patched.py
- Fix inconsistent pragma in memory.py (lines 95-96 now both marked)
- Update testpaths in pyproject.toml to include "integrations/graphiti/tests"
- Remove duplicate test___init__.py file
- Remove coverage.json from git and add to .gitignore

Code reduction: 598 deletions vs 310 insertions
All 666 tests passing.

* fix: address detailed PR review feedback on test files

Fixes:
- test_client.py: Removed redundant _apply_ladybug_monkeypatch() call, fixed convoluted pywin32 assertion, used call.kwargs directly
- test_cross_encoder.py: Extracted duplicate sys.modules mocking into graphiti_core_mocks fixture
- test_kuzu_driver_patched.py: Parameterized slow tests, split test_execute_query_handles_empty_results, updated build_indices assertions to check SQL strings
- test_memory.py: Fixed fragile import mocking to only raise for graphiti_core imports
- test_migrate_embeddings.py: Created distinct MagicMock instances per iteration to avoid mutation issues
- test_provider_naming.py: Removed print statements and script-entry guard, used explicit config values, strengthened assertions
- test_providers_facade.py: Extracted expected_exports list into module-level constant
- test_providers_google.py: Extracted repeated MagicMock setup into google_genai_mock fixture
- test_providers_llm_openai.py: Replaced tautological assertions with concrete expectations and parametrized slow tests
- search.py: Fixed min_score filtering to handle None scores by normalizing to 0.0

All 667 tests passing.

* fix: address additional detailed PR review feedback

Fixes:
- search.py: Normalized result.score in get_patterns_and_gotchas and get_similar_task_outcomes to handle None values
- test_client.py: Fixed test_returns_false_when_ladybug_unavailable to ensure graphiti_core is present, extracted repeated boilerplate into graphiti_mocks fixture
- test_cross_encoder.py: Added concrete assertion for base_url value, removed original_func indirection
- test_kuzu_driver_patched.py: Added module-level MockKuzuDriver class, added DROP_FTS_INDEX assertion to test_build_indices_with_delete_existing
- test_memory.py: Fixed tautological else branch with concrete assertion
- test_migrate_embeddings.py: Renamed mock configs to match actual roles (current_config, source_config, target_config)
- test_provider_naming.py: Removed unused pytest import and unused embedding_model variable
- test_providers_google.py: Added sys.modules patching to test_google_embedder_init_import_error
- test_providers_llm_openai.py: Fixed patch target path for OpenAIClient to use consuming module's namespace

All 667 tests passing.

* fix: remove duplicate tests and improve test coverage

Fixes:
- test_client.py: Removed duplicate test_initialize_returns_false_on_ladybug_unavailable
- test_client.py: Removed duplicate test_updates_state_with_init_info
- test_cross_encoder.py: Changed unused result variable to _ discard
- test_kuzu_driver_patched.py: Removed duplicate test_execute_query_returns_rows
- test_memory.py: Added pytest.importorskip guards for graphiti_providers package
- test_provider_naming.py: Changed `if dim:` to `if dim is not None:`, converted for-loop to pytest.mark.parametrize

All 668 tests passing.

* fix: address PR review feedback - score normalization and code duplication

- Fix score normalization to correctly handle score of 0 vs None
  - Changed `getattr(result, "score", None) or 0.0` to explicit None check
  - This prevents treating a legitimate score of 0 as None

- Refactor test_client.py to eliminate code duplication
  - Created _make_mock_config() helper function for consistent mock config creation
  - Extended graphiti_mocks fixture with better documentation
  - Converted 15+ tests to use the fixture instead of duplicated boilerplate
  - Removed ~330 net lines of duplicated setup/teardown code

Addresses HIGH and MEDIUM severity issues from PR review.

* fix: address remaining medium severity PR review issues

1. Move standalone test scripts out of tests/ directory
   - Renamed test_graphiti_memory.py -> run_graphiti_memory_test.py
   - Renamed test_ollama_embedding_memory.py -> run_ollama_embedding_test.py
   - These are standalone executable scripts with argparse, not pytest tests

2. Remove fragile pytest_collection_modifyitems filtering
   - No longer needed since standalone scripts moved out of tests/
   - Only keep validator function filtering (legitimate use case)

3. Rename shadowing fixtures in test_graphiti.py
   - temp_spec_dir -> graphiti_test_spec_dir
   - temp_project_dir -> graphiti_test_project_dir
   - mock_config -> mock_graphiti_config
   - mock_state -> mock_graphiti_state
   - Names now indicate intentional difference from conftest fixtures

Addresses 3 MEDIUM severity issues from PR review.

* fix: update test_graphiti_connection for embedded LadybugDB

The function was using outdated FalkorDB configuration attributes
(falkordb_host, falkordb_port, falkordb_password) that no longer exist
on GraphitiConfig. Updated to use embedded LadybugDB via
create_patched_kuzu_driver with db_path instead.

- Replace FalkorDriver with patched KuzuDriver for embedded DB
- Use config.get_db_path() instead of host/port credentials
- Update tests to mock the new driver creation path
- Rename test to reflect new driver type

* fix: address PR review feedback on conftest fixtures and test comments

- Fix mock_config fixture to use actual GraphitiConfig fields (database
  instead of dataset_name, openai_model instead of llm_model, etc.)
- Fix mock_state fixture to use actual GraphitiState fields
- Fix mock_env_vars to use correct env var names (GRAPHITI_DATABASE,
  OPENAI_MODEL, OPENAI_EMBEDDING_MODEL)
- Fix test_search.py comments to accurately describe None->0.0 score
  conversion, add assertion to verify the behavior
- Update pyproject.toml testpaths to include core/workspace/tests
  and remove non-existent 'tests' directory

* fix: address all remaining PR review feedback including LOW severity

MEDIUM fixes:
- Update usage docs in run_graphiti_memory_test.py to reference new filename
- Update usage docs in run_ollama_embedding_test.py to reference new filename

LOW fixes:
- Fix get_relevant_context docstring: add min_score param, correct
  include_project_context description (works in SPEC mode, not PROJECT mode)
- Make mock_embedder fixture deterministic using [0.1] * 1536 instead of
  random values for reproducibility
- Add test coverage for None score handling in get_similar_task_outcomes
  and get_patterns_and_gotchas methods

---------

Co-authored-by: StillKnotKnown <stillknotknown@users.noreply.github.com>
2026-02-12 11:40:54 +02:00

1084 lines
44 KiB
Python

"""
Unit tests for integrations.graphiti.queries_pkg.client module.
Tests for:
- _apply_ladybug_monkeypatch() function
- GraphitiClient class
Note: These tests use extensive mocking to avoid requiring graphiti_core,
real_ladybug, or other heavy dependencies to be installed.
"""
import builtins
import sys
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from integrations.graphiti.queries_pkg.client import (
GraphitiClient,
_apply_ladybug_monkeypatch,
)
@pytest.fixture(autouse=True)
def clean_modules():
"""Clean up sys.modules before and after each test."""
# Store original modules
original_modules = {
"graphiti_core": sys.modules.get("graphiti_core"),
"integrations.graphiti.queries_pkg.kuzu_driver_patched": sys.modules.get(
"integrations.graphiti.queries_pkg.kuzu_driver_patched"
),
"kuzu": sys.modules.get("kuzu"),
}
# Remove modules before test
for mod in [
"graphiti_core",
"integrations.graphiti.queries_pkg.kuzu_driver_patched",
"kuzu",
]:
sys.modules.pop(mod, None)
yield
# Clean up after test
for mod in [
"graphiti_core",
"integrations.graphiti.queries_pkg.kuzu_driver_patched",
"kuzu",
]:
sys.modules.pop(mod, None)
# Restore original modules if they existed
for mod, original in original_modules.items():
if original is not None:
sys.modules[mod] = original
@pytest.fixture
def graphiti_mocks():
"""Set up common graphiti mocks for GraphitiClient initialization tests.
This fixture handles sys.modules injection and cleanup, eliminating
the need for try/finally blocks in individual tests.
"""
mock_llm_client = MagicMock()
mock_embedder = MagicMock()
mock_driver = MagicMock()
# Create mock Graphiti instance
mock_graphiti_instance = AsyncMock()
mock_graphiti_instance.build_indices_and_constraints = AsyncMock()
mock_graphiti_class = MagicMock(return_value=mock_graphiti_instance)
# Mock graphiti_core module
mock_graphiti_core = MagicMock()
mock_graphiti_core.Graphiti = mock_graphiti_class
# Mock kuzu_driver_patched module
mock_kuzu_driver_patched = MagicMock()
mock_kuzu_driver_patched.create_patched_kuzu_driver = MagicMock(
return_value=mock_driver
)
# Inject into sys.modules
sys.modules["graphiti_core"] = mock_graphiti_core
sys.modules["integrations.graphiti.queries_pkg.kuzu_driver_patched"] = (
mock_kuzu_driver_patched
)
yield {
"mock_llm_client": mock_llm_client,
"mock_embedder": mock_embedder,
"mock_driver": mock_driver,
"mock_graphiti_instance": mock_graphiti_instance,
"mock_graphiti_class": mock_graphiti_class,
"mock_graphiti_core": mock_graphiti_core,
"mock_kuzu_driver_patched": mock_kuzu_driver_patched,
}
# Cleanup
sys.modules.pop("graphiti_core", None)
sys.modules.pop("integrations.graphiti.queries_pkg.kuzu_driver_patched", None)
def _make_mock_config(**kwargs):
"""Create a mock config with sensible defaults for GraphitiClient tests."""
mock_config = MagicMock()
mock_config.llm_provider = kwargs.get("llm_provider", "openai")
mock_config.embedder_provider = kwargs.get("embedder_provider", "openai")
mock_config.database = kwargs.get("database", "test_db")
mock_config.get_db_path.return_value = kwargs.get("db_path", Path("/test/db"))
mock_config.get_provider_summary.return_value = kwargs.get(
"provider_summary", "LLM: openai, Embedder: openai"
)
return mock_config
@pytest.fixture
def isolate_kuzu_module():
"""Isolate sys.modules['kuzu'] for tests that modify it."""
original_kuzu = sys.modules.pop("kuzu", None)
yield
if original_kuzu:
sys.modules["kuzu"] = original_kuzu
elif "kuzu" in sys.modules:
del sys.modules["kuzu"]
# =============================================================================
# Tests for _apply_ladybug_monkeypatch()
# =============================================================================
class TestApplyLadybugMonkeypatch:
"""Tests for the _apply_ladybug_monkeypatch function."""
def test_returns_true_when_real_ladybug_imports_successfully(
self, isolate_kuzu_module
):
"""Returns True when real_ladybug imports successfully."""
mock_ladybug = MagicMock()
# Mock the import statement by patching __import__
def import_side_effect(name, *args, **kwargs):
if name == "real_ladybug":
return mock_ladybug
# Fall through to original import for other modules
return original_import(name, *args, **kwargs)
original_import = builtins.__import__
with patch("builtins.__import__", side_effect=import_side_effect):
assert _apply_ladybug_monkeypatch() is True
assert sys.modules.get("kuzu") == mock_ladybug
def test_patches_sys_modules_kuzu_with_real_ladybug(self, isolate_kuzu_module):
"""Patches sys.modules["kuzu"] with real_ladybug."""
mock_ladybug = MagicMock()
def import_side_effect(name, *args, **kwargs):
if name == "real_ladybug":
return mock_ladybug
return original_import(name, *args, **kwargs)
original_import = builtins.__import__
with patch("builtins.__import__", side_effect=import_side_effect):
result = _apply_ladybug_monkeypatch()
# Verify sys.modules["kuzu"] was patched
assert result is True
assert sys.modules.get("kuzu") == mock_ladybug
def test_falls_back_to_native_kuzu_if_real_ladybug_unavailable(
self, isolate_kuzu_module
):
"""Falls back to native kuzu if real_ladybug unavailable."""
mock_kuzu = MagicMock()
def import_side_effect(name, *args, **kwargs):
if name == "real_ladybug":
raise ImportError("real_ladybug not found")
elif name == "kuzu":
# Simulate what real import does - add to sys.modules
sys.modules["kuzu"] = mock_kuzu
return mock_kuzu
return original_import(name, *args, **kwargs)
original_import = builtins.__import__
with patch("builtins.__import__", side_effect=import_side_effect):
result = _apply_ladybug_monkeypatch()
# Should return True if kuzu is available
assert result is True
# When native kuzu is imported, the import statement adds it to sys.modules
assert sys.modules.get("kuzu") == mock_kuzu
def test_returns_false_when_neither_available(self, isolate_kuzu_module):
"""Returns False when neither real_ladybug nor kuzu available."""
def import_side_effect(name, *args, **kwargs):
if name == "real_ladybug":
raise ImportError("real_ladybug not found")
elif name == "kuzu":
raise ImportError("kuzu not found")
return original_import(name, *args, **kwargs)
original_import = builtins.__import__
with patch("builtins.__import__", side_effect=import_side_effect):
result = _apply_ladybug_monkeypatch()
assert result is False
def test_windows_pywin32_error_handling(self, isolate_kuzu_module):
"""Windows-specific pywin32 error handling."""
# Create an ImportError with pywin32-related name
import_error = ImportError("No module named 'pywintypes'")
import_error.name = "pywintypes"
def import_side_effect(name, *args, **kwargs):
if name == "real_ladybug":
raise import_error
elif name == "kuzu":
raise ImportError("kuzu not found")
return original_import(name, *args, **kwargs)
original_import = builtins.__import__
with patch.object(sys, "platform", "win32"):
with patch.object(sys, "version_info", (3, 12, 0)):
with patch("builtins.__import__", side_effect=import_side_effect):
with patch(
"integrations.graphiti.queries_pkg.client.logger"
) as mock_logger:
result = _apply_ladybug_monkeypatch()
# Should log specific error about pywin32
mock_logger.error.assert_called()
error_msg = str(mock_logger.error.call_args)
assert "pywin32" in error_msg or "pywintypes" in error_msg
def test_windows_pywin32_error_detected_by_string_match(self, isolate_kuzu_module):
"""Windows pywin32 error detected by string match when name unavailable."""
# Create ImportError without name attribute (some Python versions)
import_error = ImportError("DLL load failed while importing pywintypes")
def import_side_effect(name, *args, **kwargs):
if name == "real_ladybug":
raise import_error
elif name == "kuzu":
raise ImportError("kuzu not found")
return original_import(name, *args, **kwargs)
original_import = builtins.__import__
with patch.object(sys, "platform", "win32"):
with patch.object(sys, "version_info", (3, 12, 0)):
with patch("builtins.__import__", side_effect=import_side_effect):
with patch(
"integrations.graphiti.queries_pkg.client.logger"
) as mock_logger:
result = _apply_ladybug_monkeypatch()
# Should detect pywin32 error via string match
mock_logger.error.assert_called()
error_msg = str(mock_logger.error.call_args)
assert "pywin32" in error_msg
def test_non_windows_pywin32_error_does_not_trigger_special_handling(
self, isolate_kuzu_module
):
"""Non-Windows pywin32-like error doesn't trigger special handling."""
import_error = ImportError("pywintypes not found")
def import_side_effect(name, *args, **kwargs):
if name == "real_ladybug":
raise import_error
elif name == "kuzu":
raise ImportError("kuzu not found")
return original_import(name, *args, **kwargs)
original_import = builtins.__import__
with patch.object(sys, "platform", "linux"):
with patch("builtins.__import__", side_effect=import_side_effect):
with patch(
"integrations.graphiti.queries_pkg.client.logger"
) as mock_logger:
result = _apply_ladybug_monkeypatch()
# Should use debug, not error (non-Windows)
# The function should still log debug, but not error about pywin32
assert all(
"pywin32" not in str(call)
for call in mock_logger.error.call_args_list
)
def test_windows_python_311_does_not_show_pywin32_error(self, isolate_kuzu_module):
"""Windows Python 3.11 doesn't show pywin32-specific error."""
import_error = ImportError("real_ladybug not found")
def import_side_effect(name, *args, **kwargs):
if name == "real_ladybug":
raise import_error
elif name == "kuzu":
raise ImportError("kuzu not found")
return original_import(name, *args, **kwargs)
original_import = builtins.__import__
with patch.object(sys, "platform", "win32"):
with patch.object(sys, "version_info", (3, 11, 0)): # Python 3.11
with patch("builtins.__import__", side_effect=import_side_effect):
with patch(
"integrations.graphiti.queries_pkg.client.logger"
) as mock_logger:
result = _apply_ladybug_monkeypatch()
# Should not show pywin32 error for Python 3.11
for call in mock_logger.error.call_args_list:
assert "pywin32" not in str(call)
def test_windows_non_pywin32_import_error_logs_debug(self, isolate_kuzu_module):
"""Windows non-pywin32 import error logs debug message."""
# Import error that doesn't contain 'pywintypes'
import_error = ImportError("DLL load failed while importing real_ladybug")
def import_side_effect(name, *args, **kwargs):
if name == "real_ladybug":
raise import_error
elif name == "kuzu":
raise ImportError("kuzu not found")
return original_import(name, *args, **kwargs)
original_import = builtins.__import__
with patch.object(sys, "platform", "win32"):
with patch("builtins.__import__", side_effect=import_side_effect):
with patch(
"integrations.graphiti.queries_pkg.client.logger"
) as mock_logger:
result = _apply_ladybug_monkeypatch()
# Should log debug for Windows-specific import issue
assert any(
"Windows-specific import issue" in str(call)
for call in mock_logger.debug.call_args_list
)
# =============================================================================
# Tests for GraphitiClient.__init__
# =============================================================================
class TestGraphitiClientInit:
"""Tests for GraphitiClient initialization."""
def test_sets_config_attribute(self):
"""Sets config attribute."""
mock_config = MagicMock()
client = GraphitiClient(mock_config)
assert client.config is mock_config
def test_initializes_all_attributes_to_none(self):
"""Initializes all _ attributes to None."""
mock_config = MagicMock()
client = GraphitiClient(mock_config)
assert client._graphiti is None
assert client._driver is None
assert client._llm_client is None
assert client._embedder is None
assert client._initialized is False
# =============================================================================
# Tests for GraphitiClient.initialize()
# =============================================================================
class TestGraphitiClientInitialize:
"""Tests for GraphitiClient.initialize method."""
@pytest.mark.asyncio
async def test_returns_true_if_already_initialized(self):
"""Returns True if already initialized (idempotent)."""
mock_config = MagicMock()
client = GraphitiClient(mock_config)
client._initialized = True
result = await client.initialize()
assert result is True # Should return True since already initialized
@pytest.mark.asyncio
async def test_creates_llm_client_via_factory(self, graphiti_mocks):
"""Creates LLM client via factory."""
mock_config = MagicMock()
mock_config.llm_provider = "openai"
mock_config.embedder_provider = "openai"
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client._apply_ladybug_monkeypatch"
) as mock_patch:
mock_create_llm.return_value = graphiti_mocks["mock_llm_client"]
mock_create_emb.return_value = graphiti_mocks["mock_embedder"]
mock_patch.return_value = True
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is True
mock_create_llm.assert_called_once_with(mock_config)
@pytest.mark.asyncio
async def test_creates_embedder_via_factory(self, graphiti_mocks):
"""Creates embedder via factory."""
mock_config = _make_mock_config()
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client._apply_ladybug_monkeypatch"
) as mock_patch:
mock_create_llm.return_value = graphiti_mocks["mock_llm_client"]
mock_create_emb.return_value = graphiti_mocks["mock_embedder"]
mock_patch.return_value = True
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is True
mock_create_emb.assert_called_once_with(mock_config)
@pytest.mark.asyncio
async def test_applies_ladybug_monkeypatch(self, graphiti_mocks):
"""Applies ladybug monkeypatch."""
mock_config = _make_mock_config()
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client._apply_ladybug_monkeypatch"
) as mock_patch:
mock_create_llm.return_value = graphiti_mocks["mock_llm_client"]
mock_create_emb.return_value = graphiti_mocks["mock_embedder"]
mock_patch.return_value = True
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is True
mock_patch.assert_called_once()
@pytest.mark.asyncio
async def test_creates_patched_kuzu_driver(self, graphiti_mocks):
"""Creates patched KuzuDriver."""
mock_config = _make_mock_config()
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client._apply_ladybug_monkeypatch"
) as mock_patch:
mock_create_llm.return_value = graphiti_mocks["mock_llm_client"]
mock_create_emb.return_value = graphiti_mocks["mock_embedder"]
mock_patch.return_value = True
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is True
graphiti_mocks[
"mock_kuzu_driver_patched"
].create_patched_kuzu_driver.assert_called_once_with(
db=str(Path("/test/db"))
)
@pytest.mark.asyncio
async def test_builds_indices_on_first_init(self, graphiti_mocks):
"""Builds indices on first init."""
mock_config = _make_mock_config()
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client._apply_ladybug_monkeypatch"
) as mock_patch:
mock_create_llm.return_value = graphiti_mocks["mock_llm_client"]
mock_create_emb.return_value = graphiti_mocks["mock_embedder"]
mock_patch.return_value = True
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is True
graphiti_mocks[
"mock_graphiti_instance"
].build_indices_and_constraints.assert_called_once()
@pytest.mark.asyncio
async def test_builds_indices_with_state_update(self, graphiti_mocks):
"""Builds indices and updates state on first init."""
from integrations.graphiti.config import GraphitiState
mock_config = _make_mock_config()
state = GraphitiState()
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client._apply_ladybug_monkeypatch"
) as mock_patch:
mock_create_llm.return_value = graphiti_mocks["mock_llm_client"]
mock_create_emb.return_value = graphiti_mocks["mock_embedder"]
mock_patch.return_value = True
client = GraphitiClient(mock_config)
result = await client.initialize(state)
assert result is True
assert state.indices_built is True
assert state.initialized is True
assert state.database == "test_db"
assert state.llm_provider == "openai"
assert state.embedder_provider == "openai"
assert state.created_at is not None
@pytest.mark.asyncio
async def test_returns_true_on_success(self, graphiti_mocks):
"""Returns True on success."""
mock_config = _make_mock_config()
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client._apply_ladybug_monkeypatch"
) as mock_patch:
mock_create_llm.return_value = graphiti_mocks["mock_llm_client"]
mock_create_emb.return_value = graphiti_mocks["mock_embedder"]
mock_patch.return_value = True
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is True
@pytest.mark.asyncio
async def test_returns_false_when_provider_not_installed_raised_llm(
self, graphiti_mocks
):
"""Returns False when ProviderNotInstalled raised for LLM."""
from integrations.graphiti.providers_pkg import ProviderNotInstalled
mock_config = _make_mock_config()
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch(
"integrations.graphiti.queries_pkg.client.capture_exception"
) as mock_capture:
mock_create_llm.side_effect = ProviderNotInstalled(
"openai not installed"
)
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is False
mock_capture.assert_called_once()
@pytest.mark.asyncio
async def test_returns_false_when_provider_error_raised_llm(self, graphiti_mocks):
"""Returns False when ProviderError raised for LLM."""
from integrations.graphiti.providers_pkg import ProviderError
mock_config = _make_mock_config()
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch(
"integrations.graphiti.queries_pkg.client.capture_exception"
) as mock_capture:
mock_create_llm.side_effect = ProviderError("LLM config error")
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is False
mock_capture.assert_called_once()
@pytest.mark.asyncio
async def test_returns_false_when_provider_not_installed_raised_embedder(
self, graphiti_mocks
):
"""Returns False when ProviderNotInstalled raised for embedder."""
from integrations.graphiti.providers_pkg import ProviderNotInstalled
mock_config = _make_mock_config()
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client.capture_exception"
) as mock_capture:
mock_create_llm.return_value = graphiti_mocks["mock_llm_client"]
mock_create_emb.side_effect = ProviderNotInstalled(
"embedder not installed"
)
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is False
mock_capture.assert_called()
@pytest.mark.asyncio
async def test_returns_false_when_provider_error_raised_embedder(
self, graphiti_mocks
):
"""Returns False when ProviderError raised for embedder."""
from integrations.graphiti.providers_pkg import ProviderError
mock_config = _make_mock_config()
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client.capture_exception"
) as mock_capture:
mock_create_llm.return_value = graphiti_mocks["mock_llm_client"]
mock_create_emb.side_effect = ProviderError("Embedder config error")
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is False
mock_capture.assert_called()
@pytest.mark.asyncio
async def test_returns_false_when_ladybug_unavailable(self, graphiti_mocks):
"""Returns False when ladybug unavailable."""
mock_config = _make_mock_config()
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client._apply_ladybug_monkeypatch"
) as mock_patch:
mock_create_llm.return_value = graphiti_mocks["mock_llm_client"]
mock_create_emb.return_value = graphiti_mocks["mock_embedder"]
mock_patch.return_value = False # Ladybug unavailable
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is False
@pytest.mark.asyncio
async def test_returns_false_on_database_init_os_error(self, graphiti_mocks):
"""Returns False on database init OSError."""
mock_config = _make_mock_config()
# Override the mock to raise OSError
graphiti_mocks[
"mock_kuzu_driver_patched"
].create_patched_kuzu_driver.side_effect = OSError("Permission denied")
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client._apply_ladybug_monkeypatch"
) as mock_patch:
with patch(
"integrations.graphiti.queries_pkg.client.capture_exception"
) as mock_capture:
mock_create_llm.return_value = graphiti_mocks["mock_llm_client"]
mock_create_emb.return_value = graphiti_mocks["mock_embedder"]
mock_patch.return_value = True
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is False
mock_capture.assert_called()
@pytest.mark.asyncio
async def test_returns_false_on_database_init_permission_error(
self, graphiti_mocks
):
"""Returns False on database init PermissionError."""
mock_config = _make_mock_config()
# Override the mock to raise PermissionError
graphiti_mocks[
"mock_kuzu_driver_patched"
].create_patched_kuzu_driver.side_effect = PermissionError("Access denied")
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client._apply_ladybug_monkeypatch"
) as mock_patch:
with patch(
"integrations.graphiti.queries_pkg.client.capture_exception"
) as mock_capture:
mock_create_llm.return_value = graphiti_mocks["mock_llm_client"]
mock_create_emb.return_value = graphiti_mocks["mock_embedder"]
mock_patch.return_value = True
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is False
mock_capture.assert_called()
@pytest.mark.asyncio
async def test_returns_false_on_database_init_generic_exception(
self, graphiti_mocks
):
"""Returns False on database init generic Exception."""
mock_config = _make_mock_config()
# Override the mock to raise RuntimeError
graphiti_mocks[
"mock_kuzu_driver_patched"
].create_patched_kuzu_driver.side_effect = RuntimeError("Unexpected error")
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client._apply_ladybug_monkeypatch"
) as mock_patch:
with patch(
"integrations.graphiti.queries_pkg.client.capture_exception"
) as mock_capture:
mock_create_llm.return_value = graphiti_mocks["mock_llm_client"]
mock_create_emb.return_value = graphiti_mocks["mock_embedder"]
mock_patch.return_value = True
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is False
mock_capture.assert_called()
@pytest.mark.asyncio
async def test_returns_false_on_graphiti_construction_exception(self):
"""Returns False on Graphiti construction generic Exception (lines 278-286)."""
mock_config = MagicMock()
mock_config.llm_provider = "openai"
mock_config.embedder_provider = "openai"
mock_config.get_db_path.return_value = Path("/test/db")
# Create a Graphiti class that raises exception during construction
mock_graphiti_class = MagicMock(
side_effect=ValueError("Graphiti construction failed")
)
# Mock graphiti_core module
mock_graphiti_core = MagicMock()
mock_graphiti_core.Graphiti = mock_graphiti_class
sys.modules["graphiti_core"] = mock_graphiti_core
# Create mock kuzu driver to avoid import errors
mock_kuzu = MagicMock()
mock_kuzu_driver = MagicMock()
mock_kuzu.Database = MagicMock()
mock_kuzu_driver.create_patched_kuzu_driver = MagicMock(return_value=mock_kuzu)
sys.modules["kuzu"] = mock_kuzu
sys.modules["integrations.graphiti.queries_pkg.kuzu_driver_patched"] = (
mock_kuzu_driver
)
try:
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client.capture_exception"
) as mock_capture:
mock_create_llm.return_value = MagicMock()
mock_create_emb.return_value = MagicMock()
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is False
# Verify capture_exception was called with generic exception type
mock_capture.assert_called()
# Find the call with ValueError error_type
for call in mock_capture.call_args_list:
call_kwargs = call.kwargs
if call_kwargs.get("error_type") == "ValueError":
return
pytest.fail("ValueError exception not captured")
finally:
sys.modules.pop("graphiti_core", None)
sys.modules.pop("kuzu", None)
sys.modules.pop(
"integrations.graphiti.queries_pkg.kuzu_driver_patched", None
)
@pytest.mark.asyncio
async def test_captures_exceptions_via_sentry(self, graphiti_mocks):
"""Captures exceptions via sentry."""
from integrations.graphiti.providers_pkg import ProviderError
mock_config = _make_mock_config()
error = ProviderError("Test error")
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch(
"integrations.graphiti.queries_pkg.client.capture_exception"
) as mock_capture:
mock_create_llm.side_effect = error
client = GraphitiClient(mock_config)
await client.initialize()
# Verify capture_exception was called with correct parameters
mock_capture.assert_called_once()
call_kwargs = mock_capture.call_args[1]
assert call_kwargs["error_type"] == "ProviderError"
assert call_kwargs["provider_type"] == "llm"
@pytest.mark.asyncio
async def test_skips_building_indices_if_state_indices_built(self, graphiti_mocks):
"""Skips building indices if state.indices_built is True."""
from integrations.graphiti.config import GraphitiState
mock_config = _make_mock_config()
state = GraphitiState(indices_built=True)
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client._apply_ladybug_monkeypatch"
) as mock_patch:
mock_create_llm.return_value = graphiti_mocks["mock_llm_client"]
mock_create_emb.return_value = graphiti_mocks["mock_embedder"]
mock_patch.return_value = True
client = GraphitiClient(mock_config)
result = await client.initialize(state)
assert result is True
# Should not build indices since they were already built
graphiti_mocks[
"mock_graphiti_instance"
].build_indices_and_constraints.assert_not_called()
@pytest.mark.asyncio
async def test_handles_kuzu_driver_import_error(self):
"""Handles ImportError from kuzu_driver_patched."""
mock_config = MagicMock()
mock_config.llm_provider = "openai"
mock_config.embedder_provider = "openai"
# Create mock Graphiti instance
mock_graphiti_instance = AsyncMock()
mock_graphiti_instance.build_indices_and_constraints = AsyncMock()
mock_graphiti_class = MagicMock(return_value=mock_graphiti_instance)
# Mock graphiti_core module
mock_graphiti_core = MagicMock()
mock_graphiti_core.Graphiti = mock_graphiti_class
sys.modules["graphiti_core"] = mock_graphiti_core
try:
with patch("graphiti_providers.create_llm_client") as mock_create_llm:
with patch("graphiti_providers.create_embedder") as mock_create_emb:
with patch(
"integrations.graphiti.queries_pkg.client._apply_ladybug_monkeypatch"
) as mock_patch:
with patch(
"integrations.graphiti.queries_pkg.client.capture_exception"
) as mock_capture:
mock_create_llm.return_value = MagicMock()
mock_create_emb.return_value = MagicMock()
mock_patch.return_value = True
# Create import error that will be raised when trying to import
# We need to mock the module import itself, not just the function
def import_side_effect(name, *args, **kwargs):
if (
name
== "integrations.graphiti.queries_pkg.kuzu_driver_patched"
):
raise ImportError("kuzu_driver_patched not found")
return original_import(name, *args, **kwargs)
original_import = builtins.__import__
with patch(
"builtins.__import__", side_effect=import_side_effect
):
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is False
mock_capture.assert_called()
finally:
sys.modules.pop("graphiti_core", None)
# =============================================================================
# Tests for GraphitiClient properties
# =============================================================================
class TestGraphitiClientProperties:
"""Tests for GraphitiClient properties."""
def test_graphiti_property_returns_graphiti(self):
"""graphiti property returns _graphiti."""
mock_config = MagicMock()
client = GraphitiClient(mock_config)
mock_graphiti = MagicMock()
client._graphiti = mock_graphiti
result = client.graphiti
assert result is mock_graphiti
def test_is_initialized_returns_initialized_flag(self):
"""is_initialized returns _initialized."""
mock_config = MagicMock()
client = GraphitiClient(mock_config)
client._initialized = True
assert client.is_initialized is True
client._initialized = False
assert client.is_initialized is False
# =============================================================================
# Tests for GraphitiClient.close()
# =============================================================================
class TestGraphitiClientClose:
"""Tests for GraphitiClient.close method."""
@pytest.mark.asyncio
async def test_closes_graphiti_connection(self):
"""Closes graphiti connection."""
mock_config = MagicMock()
client = GraphitiClient(mock_config)
mock_graphiti = AsyncMock()
client._graphiti = mock_graphiti
client._driver = MagicMock()
client._llm_client = MagicMock()
client._embedder = MagicMock()
client._initialized = True
await client.close()
mock_graphiti.close.assert_called_once()
@pytest.mark.asyncio
async def test_resets_all_attributes(self):
"""Resets all attributes."""
mock_config = MagicMock()
client = GraphitiClient(mock_config)
mock_graphiti = AsyncMock()
client._graphiti = mock_graphiti
client._driver = MagicMock()
client._llm_client = MagicMock()
client._embedder = MagicMock()
client._initialized = True
await client.close()
assert client._graphiti is None
assert client._driver is None
assert client._llm_client is None
assert client._embedder is None
assert client._initialized is False
@pytest.mark.asyncio
async def test_handles_exceptions_gracefully(self):
"""Handles exceptions gracefully."""
mock_config = MagicMock()
client = GraphitiClient(mock_config)
mock_graphiti = AsyncMock()
mock_graphiti.close.side_effect = Exception("Close error")
client._graphiti = mock_graphiti
client._driver = MagicMock()
client._llm_client = MagicMock()
client._embedder = MagicMock()
client._initialized = True
# Should not raise exception
await client.close()
# Attributes should still be reset
assert client._graphiti is None
assert client._driver is None
@pytest.mark.asyncio
async def test_handles_close_when_graphiti_is_none(self):
"""Handles close when _graphiti is None."""
mock_config = MagicMock()
client = GraphitiClient(mock_config)
client._graphiti = None
# Should not raise exception
await client.close()
assert client._initialized is False
# =============================================================================
# Tests for _apply_ladybug_monkeypatch() additional scenarios
# =============================================================================
class TestApplyLadybugMonkeypatchAdditional:
"""Additional tests for ladybug monkeypatch edge cases."""
def test_logs_debug_on_ladybug_import_failure(self, isolate_kuzu_module):
"""Logs debug message when LadybugDB import fails."""
def import_side_effect(name, *args, **kwargs):
if name == "real_ladybug":
raise ImportError("real_ladybug not found")
return original_import(name, *args, **kwargs)
original_import = builtins.__import__
with patch("builtins.__import__", side_effect=import_side_effect):
with patch(
"integrations.graphiti.queries_pkg.client.logger"
) as mock_logger:
# Mock kuzu to be available for fallback
sys.modules["kuzu"] = MagicMock()
try:
result = _apply_ladybug_monkeypatch()
assert result is True
# Should log debug for ladybug failure
mock_logger.debug.assert_called()
finally:
sys.modules.pop("kuzu", None)
# =============================================================================
# Tests for GraphitiClient.initialize() ImportError paths
# =============================================================================
class TestGraphitiClientInitializeImportError:
"""Tests for GraphitiClient.initialize ImportError handling."""
@pytest.mark.asyncio
async def test_initialize_graphiti_core_import_error(self):
"""Returns False when graphiti_core import fails."""
mock_config = MagicMock()
mock_config.llm_provider = "openai"
mock_config.embedder_provider = "openai"
# Mock graphiti_core module import to raise ImportError
def import_side_effect(name, *args, **kwargs):
if name == "graphiti_core":
raise ImportError("graphiti_core not found")
elif name == "graphiti_providers":
# Return mock for providers to get past that import
mock_providers = MagicMock()
mock_providers.create_llm_client = MagicMock(return_value=MagicMock())
mock_providers.create_embedder = MagicMock(return_value=MagicMock())
mock_providers.ProviderError = Exception
mock_providers.ProviderNotInstalled = Exception
return mock_providers
return original_import(name, *args, **kwargs)
original_import = builtins.__import__
with patch("builtins.__import__", side_effect=import_side_effect):
with patch(
"integrations.graphiti.queries_pkg.client.capture_exception"
) as mock_capture:
client = GraphitiClient(mock_config)
result = await client.initialize()
assert result is False
mock_capture.assert_called()