fix: resolve CI failures - lint, tests, and review feedback

- Fix Python lint issues (Ruff I001, F821)
  - Sort/format imports in agents/__init__.py and test_memory_save.py
  - Add missing `import io` to result_parser.py
  - Remove unnecessary parentheses from INITIAL_RETRY_DELAY_SECONDS

- Fix frontend test regex for token file paths
  - Update regex pattern from /.claude-token-1234-[0-9a-f]{16}/ to /.claude-token-[0-9a-f]{32}/
  - Remove unnecessary Date.now() mock since token generation uses crypto.randomBytes()

- Fix Python test failures
  - Update test_agents_init.py to reflect actual lazy import behavior (agents.base is imported for CodeQL)
  - Add missing pytest fixtures (review_spec_dir, complete_spec_dir, approved_state, pending_state)
  - Fix test_main_comprehensive.py to actually import ui.main before checking sys.modules
  - Fix kuzu driver test to properly check for kuzu module availability
  - Add pytest_collection_modifyitems hook to exclude standalone test scripts from collection
  - Add __test__ = False to standalone graphiti test scripts

- Address Gemini Code Assist review feedback
  - Keep anthropic/claude-sonnet-4 as the correct model name for OpenRouter
  - Remove unnecessary parentheses from INITIAL_RETRY_DELAY_SECONDS = (2)

Fixes: #1721
This commit is contained in:
StillKnotKnown
2026-02-09 12:31:23 +02:00
parent 237d0dcabc
commit b4d09a4d39
19 changed files with 238 additions and 68 deletions
+6 -2
View File
@@ -73,9 +73,13 @@ def __getattr__(name: str) -> Any:
when accessed via 'from module import name' syntax.
"""
if name in ("AUTO_CONTINUE_DELAY_SECONDS", "HUMAN_INTERVENTION_FILE"):
from .base import AUTO_CONTINUE_DELAY_SECONDS, HUMAN_INTERVENTION_FILE # noqa: F401
from .base import AUTO_CONTINUE_DELAY_SECONDS, HUMAN_INTERVENTION_FILE
return locals()[name]
return (
AUTO_CONTINUE_DELAY_SECONDS
if name == "AUTO_CONTINUE_DELAY_SECONDS"
else HUMAN_INTERVENTION_FILE
)
elif name == "run_autonomous_agent":
from .coder import run_autonomous_agent
@@ -31,6 +31,9 @@ Usage:
python integrations/graphiti/test_graphiti_memory.py --test ollama
"""
# Exclude this file from pytest collection - this is a standalone test script
__test__ = False
import argparse
import asyncio
import json
@@ -45,6 +45,9 @@ Usage:
python integrations/graphiti/test_ollama_embedding_memory.py --test full-cycle
"""
# Exclude this file from pytest collection - this is a standalone test script
__test__ = False
import argparse
import asyncio
import os
@@ -2,6 +2,7 @@
JSON response parsing utilities.
"""
import io
import json
from typing import Any
@@ -33,7 +34,11 @@ class ResultParser:
# Try direct parse
try:
return json.loads(response)
except (AttributeError, io.UnsupportedOperation, OSError): # Stream doesn't support reconfigure
except (
AttributeError,
io.UnsupportedOperation,
OSError,
): # Stream doesn't support reconfigure
pass
# Try extracting from markdown code block
@@ -43,7 +48,11 @@ class ResultParser:
if end > start:
try:
return json.loads(response[start:end].strip())
except (AttributeError, io.UnsupportedOperation, OSError): # Stream doesn't support wrapper
except (
AttributeError,
io.UnsupportedOperation,
OSError,
): # Stream doesn't support wrapper
pass
# Try finding JSON object
@@ -180,12 +180,12 @@ class GitProvider(Protocol):
@property
def provider_type(self) -> ProviderType:
"""Get the provider type."""
pass
...
@property
def repo(self) -> str:
"""Get the repository in owner/repo format."""
pass
...
# -------------------------------------------------------------------------
# Pull Request Operations
@@ -201,7 +201,7 @@ class GitProvider(Protocol):
Returns:
PRData with full PR details including diff
"""
pass
...
async def fetch_prs(self, filters: PRFilters | None = None) -> list[PRData]:
"""
@@ -213,7 +213,7 @@ class GitProvider(Protocol):
Returns:
List of PRData
"""
pass
...
async def fetch_pr_diff(self, number: int) -> str:
"""
@@ -225,7 +225,7 @@ class GitProvider(Protocol):
Returns:
Unified diff string
"""
pass
...
async def post_review(
self,
@@ -242,7 +242,7 @@ class GitProvider(Protocol):
Returns:
Review ID
"""
pass
...
async def merge_pr(
self,
@@ -261,7 +261,7 @@ class GitProvider(Protocol):
Returns:
True if merged successfully
"""
pass
...
async def close_pr(
self,
@@ -278,7 +278,7 @@ class GitProvider(Protocol):
Returns:
True if closed successfully
"""
pass
...
# -------------------------------------------------------------------------
# Issue Operations
@@ -294,7 +294,7 @@ class GitProvider(Protocol):
Returns:
IssueData with full issue details
"""
pass
...
async def fetch_issues(
self, filters: IssueFilters | None = None
@@ -308,7 +308,7 @@ class GitProvider(Protocol):
Returns:
List of IssueData
"""
pass
...
async def create_issue(
self,
@@ -329,7 +329,7 @@ class GitProvider(Protocol):
Returns:
Created IssueData
"""
pass
...
async def close_issue(
self,
@@ -346,7 +346,7 @@ class GitProvider(Protocol):
Returns:
True if closed successfully
"""
pass
...
async def add_comment(
self,
@@ -363,7 +363,7 @@ class GitProvider(Protocol):
Returns:
Comment ID
"""
pass
...
# -------------------------------------------------------------------------
# Label Operations
@@ -381,7 +381,7 @@ class GitProvider(Protocol):
issue_or_pr_number: Issue/PR number
labels: Labels to apply
"""
pass
...
async def remove_labels(
self,
@@ -395,7 +395,7 @@ class GitProvider(Protocol):
issue_or_pr_number: Issue/PR number
labels: Labels to remove
"""
pass
...
async def create_label(
self,
@@ -407,7 +407,7 @@ class GitProvider(Protocol):
Args:
label: Label data
"""
pass
...
async def list_labels(self) -> list[LabelData]:
"""
@@ -416,7 +416,7 @@ class GitProvider(Protocol):
Returns:
List of LabelData
"""
pass
...
# -------------------------------------------------------------------------
# Repository Operations
@@ -429,7 +429,7 @@ class GitProvider(Protocol):
Returns:
Repository metadata
"""
pass
...
async def get_default_branch(self) -> str:
"""
@@ -438,7 +438,7 @@ class GitProvider(Protocol):
Returns:
Default branch name (e.g., "main", "master")
"""
pass
...
async def check_permissions(self, username: str) -> str:
"""
@@ -450,7 +450,7 @@ class GitProvider(Protocol):
Returns:
Permission level (admin, write, read, none)
"""
pass
...
# -------------------------------------------------------------------------
# API Operations (Low-level)
@@ -471,7 +471,7 @@ class GitProvider(Protocol):
Returns:
API response data
"""
pass
...
async def api_post(
self,
@@ -488,4 +488,4 @@ class GitProvider(Protocol):
Returns:
API response data
"""
pass
...
+12 -4
View File
@@ -97,10 +97,18 @@ async def test_memory_imports():
# Test graphiti queries_pkg imports
try:
from integrations.graphiti.queries_pkg.client import GraphitiClient # noqa: F401
from integrations.graphiti.queries_pkg.graphiti import GraphitiMemory # noqa: F401
from integrations.graphiti.queries_pkg.queries import GraphitiQueries # noqa: F401
from integrations.graphiti.queries_pkg.search import GraphitiSearch # noqa: F401
from integrations.graphiti.queries_pkg.client import ( # noqa: F401
GraphitiClient,
)
from integrations.graphiti.queries_pkg.graphiti import ( # noqa: F401
GraphitiMemory,
)
from integrations.graphiti.queries_pkg.queries import ( # noqa: F401
GraphitiQueries,
)
from integrations.graphiti.queries_pkg.search import ( # noqa: F401
GraphitiSearch,
)
print("[OK] integrations.graphiti.queries_pkg imports successful")
except ImportError as e:
+4 -4
View File
@@ -24,7 +24,7 @@ export function ensureDir(dirPath: string): boolean {
}
return true;
} catch (error) {
console.error(`[fs-utils] Failed to create directory ${dirPath}:`, error);
console.error(`[fs-utils] Failed to create directory ${dirPath}:`, error instanceof Error ? error.message : String(error));
return false;
}
}
@@ -102,7 +102,7 @@ export function safeWriteFile(filePath: string, content: string): string {
fs.writeFileSync(writablePath, content, 'utf-8');
return writablePath;
} catch (error) {
console.error(`[fs-utils] Failed to write file ${writablePath}:`, error);
console.error(`[fs-utils] Failed to write file ${writablePath}:`, error instanceof Error ? error.message : String(error));
throw error;
}
}
@@ -120,7 +120,7 @@ export function safeReadFile(originalPath: string): string | null {
return fs.readFileSync(originalPath, 'utf-8');
}
} catch (error) {
console.error(`[fs-utils] Failed to read file ${originalPath}:`, error);
console.error(`[fs-utils] Failed to read file ${originalPath}:`, error instanceof Error ? error.message : String(error));
// Fall through to try XDG fallback
}
@@ -133,7 +133,7 @@ export function safeReadFile(originalPath: string): string | null {
return fs.readFileSync(fallbackPath, 'utf-8');
}
} catch (error) {
console.error(`[fs-utils] Failed to read fallback file ${fallbackPath}:`, error);
console.error(`[fs-utils] Failed to read fallback file ${fallbackPath}:`, error instanceof Error ? error.message : String(error));
}
}
@@ -796,7 +796,7 @@ export function registerTaskExecutionHandlers(
return { success: true };
} catch (error) {
console.error('Failed to update task status:', error);
console.error('Failed to update task status:', error instanceof Error ? error.message : String(error));
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to update task status'
@@ -874,7 +874,7 @@ export function registerTaskExecutionHandlers(
return { success: true };
} catch (error) {
console.error('[TASK_RESUME_PAUSED] Failed to write RESUME file:', error);
console.error('[TASK_RESUME_PAUSED] Failed to write RESUME file:', error instanceof Error ? error.message : String(error));
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to signal resume'
@@ -1233,7 +1233,7 @@ export function registerTaskExecutionHandlers(
}
};
} catch (error) {
console.error('Failed to recover stuck task:', error);
console.error('Failed to recover stuck task:', error instanceof Error ? error.message : String(error));
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to recover task'
+3 -3
View File
@@ -128,7 +128,7 @@ export class LogService {
appendFileSync(session.logPath, content);
this.logBuffers.set(taskId, []); // Clear buffer
} catch (error) {
console.error(`[LogService] Failed to flush logs for task ${taskId}:`, error);
console.error(`[LogService] Failed to flush logs for task ${taskId}:`, error instanceof Error ? error.message : String(error));
}
}
@@ -167,7 +167,7 @@ export class LogService {
const logContent = readFileSync(session.logPath, 'utf-8');
writeFileSync(latestPath, logContent, 'utf-8');
} catch (error) {
console.error(`[LogService] Failed to end session for task ${taskId}:`, error);
console.error(`[LogService] Failed to end session for task ${taskId}:`, error instanceof Error ? error.message : String(error));
}
// Clean up
@@ -264,7 +264,7 @@ export class LogService {
try {
return readFileSync(logFile, 'utf-8');
} catch (error) {
console.error(`[LogService] Failed to load logs from ${logFile}:`, error);
console.error(`[LogService] Failed to load logs from ${logFile}:`, error instanceof Error ? error.message : String(error));
return '';
}
}
+2 -2
View File
@@ -38,7 +38,7 @@ export function readSettingsFile(): Record<string, unknown> | undefined {
const errorCode = (error as NodeJS.ErrnoException)?.code;
if (errorCode !== 'ENOENT') {
// Log unexpected errors but don't crash
console.error('Settings file read error:', error);
console.error('Settings file read error:', error instanceof Error ? error.message : String(error));
}
return undefined;
}
@@ -82,7 +82,7 @@ export async function readSettingsFileAsync(): Promise<Record<string, unknown> |
const errorCode = (error as NodeJS.ErrnoException)?.code;
if (errorCode !== 'ENOENT') {
// Log unexpected errors but don't crash
console.error('Settings file async read error:', error);
console.error('Settings file async read error:', error instanceof Error ? error.message : String(error));
}
return undefined;
}
@@ -185,7 +185,7 @@ export class TerminalSessionStore {
console.warn('[TerminalSessionStore] Version mismatch, resetting sessions');
return { success: false };
} catch (error) {
console.error(`[TerminalSessionStore] Error loading ${filePath}:`, error);
console.error(`[TerminalSessionStore] Error loading ${filePath}:`, error instanceof Error ? error.message : String(error));
return { success: false, error: error as Error };
}
}
@@ -224,7 +224,7 @@ export class TerminalSessionStore {
// Step 3: Atomic rename temp to target
renameSync(this.tempPath, this.storePath);
} catch (error) {
console.error('[TerminalSessionStore] Error saving sessions:', error);
console.error('[TerminalSessionStore] Error saving sessions:', error instanceof Error ? error.message : String(error));
// Clean up temp file if it exists
try {
if (existsSync(this.tempPath)) {
@@ -293,7 +293,7 @@ export class TerminalSessionStore {
this.consecutiveFailures = 0;
} catch (error) {
this.consecutiveFailures++;
console.error('[TerminalSessionStore] Error saving sessions:', error);
console.error('[TerminalSessionStore] Error saving sessions:', error instanceof Error ? error.message : String(error));
// Clean up temp file if it exists
try {
@@ -268,7 +268,6 @@ describe('claude-integration-handler', () => {
env: { PATH: '/opt/claude/bin:/usr/bin' },
});
mockGetClaudeProfileManager.mockReturnValue(profileManager);
const nowSpy = vi.spyOn(Date, 'now').mockReturnValue(1234);
const terminal = createMockTerminal({ id: 'term-3' });
@@ -277,9 +276,10 @@ describe('claude-integration-handler', () => {
const tokenPath = vi.mocked(writeFileSync).mock.calls[0]?.[0] as string;
const tokenContents = vi.mocked(writeFileSync).mock.calls[0]?.[1] as string;
const tokenPrefix = path.join(tmpdir(), '.claude-token-1234-');
// The actual implementation uses crypto.randomBytes(16).toString('hex') which generates 32 hex chars
const tokenPrefix = path.join(tmpdir(), '.claude-token-');
const tokenExt = getTempFileExtension(platform);
expect(tokenPath).toMatch(new RegExp(`^${escapeForRegex(tokenPrefix)}[0-9a-f]{16}${escapeForRegex(tokenExt)}$`));
expect(tokenPath).toMatch(new RegExp(`^${escapeForRegex(tokenPrefix)}[0-9a-f]{32}${escapeForRegex(tokenExt)}$`));
expect(tokenContents).toBe(getTokenFileContent(platform, 'token-value'));
const written = mockWriteToPty.mock.calls[0][1] as string;
@@ -294,8 +294,6 @@ describe('claude-integration-handler', () => {
expect(written).toContain(`${cmdQuote}${command}${cmdQuote}`);
expect(profileManager.getProfile).toHaveBeenCalledWith('prof-1');
expect(mockPersistSession).toHaveBeenCalledWith(terminal);
nowSpy.mockRestore();
});
it('prefers the config dir flow when profile has both oauth token and config dir', async () => {
@@ -142,7 +142,7 @@ class PtyDaemonClient {
// Wait for daemon to start listening
await new Promise((resolve) => setTimeout(resolve, 1000));
} catch (error) {
console.error('[PtyDaemonClient] Failed to spawn daemon:', error);
console.error('[PtyDaemonClient] Failed to spawn daemon:', error instanceof Error ? error.message : String(error));
throw error;
}
}
@@ -101,7 +101,7 @@ class PtyDaemon {
fs.unlinkSync(SOCKET_PATH);
console.error('[PTY Daemon] Cleaned up stale socket');
} catch (error) {
console.error('[PTY Daemon] Failed to clean up socket:', error);
console.error('[PTY Daemon] Failed to clean up socket:', error instanceof Error ? error.message : String(error));
}
}
}
@@ -130,7 +130,7 @@ class PtyDaemon {
try {
fs.chmodSync(SOCKET_PATH, 0o600);
} catch (error) {
console.error('[PTY Daemon] Failed to set socket permissions:', error);
console.error('[PTY Daemon] Failed to set socket permissions:', error instanceof Error ? error.message : String(error));
}
}
});
@@ -324,7 +324,7 @@ class PtyDaemon {
return id;
} catch (error) {
console.error('[PTY Daemon] Failed to create PTY:', error);
console.error('[PTY Daemon] Failed to create PTY:', error instanceof Error ? error.message : String(error));
throw error;
}
}
@@ -524,7 +524,7 @@ if (require.main === module) {
new PtyDaemon();
console.error('[PTY Daemon] Running - PID:', process.pid);
} catch (error) {
console.error('[PTY Daemon] Fatal error:', error);
console.error('[PTY Daemon] Fatal error:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
}
@@ -95,7 +95,7 @@ class SessionPersistence {
);
return validSessions;
} catch (error) {
console.error('[SessionPersistence] Failed to load sessions:', error);
console.error('[SessionPersistence] Failed to load sessions:', error instanceof Error ? error.message : String(error));
return [];
}
}
@@ -190,7 +190,7 @@ class SessionPersistence {
this.saveSession(session);
console.warn(`[SessionPersistence] Saved buffer for session ${sessionId} (${serializedBuffer.length} bytes)`);
} catch (error) {
console.error(`[SessionPersistence] Failed to save buffer for ${sessionId}:`, error);
console.error(`[SessionPersistence] Failed to save buffer for ${sessionId}:`, error instanceof Error ? error.message : String(error));
}
}
@@ -210,7 +210,7 @@ class SessionPersistence {
try {
return fs.readFileSync(bufferPath, 'utf8');
} catch (error) {
console.error(`[SessionPersistence] Failed to load buffer for ${sessionId}:`, error);
console.error(`[SessionPersistence] Failed to load buffer for ${sessionId}:`, error instanceof Error ? error.message : String(error));
return null;
}
}
@@ -225,7 +225,7 @@ class SessionPersistence {
fs.unlinkSync(bufferPath);
console.warn(`[SessionPersistence] Deleted buffer file: ${bufferFile}`);
} catch (error) {
console.error(`[SessionPersistence] Failed to delete buffer file ${bufferFile}:`, error);
console.error(`[SessionPersistence] Failed to delete buffer file ${bufferFile}:`, error instanceof Error ? error.message : String(error));
}
}
}
@@ -268,7 +268,7 @@ class SessionPersistence {
fs.writeFileSync(SESSIONS_FILE, JSON.stringify(data, null, 2), 'utf8');
console.warn(`[SessionPersistence] Saved ${data.sessions.length} sessions to disk`);
} catch (error) {
console.error('[SessionPersistence] Failed to save sessions:', error);
console.error('[SessionPersistence] Failed to save sessions:', error instanceof Error ? error.message : String(error));
}
}
@@ -299,7 +299,7 @@ class SessionPersistence {
console.warn(`[SessionPersistence] Cleaned up ${cleanedCount} orphaned buffer files`);
}
} catch (error) {
console.error('[SessionPersistence] Failed to cleanup orphaned buffers:', error);
console.error('[SessionPersistence] Failed to cleanup orphaned buffers:', error instanceof Error ? error.message : String(error));
}
}
}
+10 -7
View File
@@ -62,18 +62,21 @@ class TestModuleInitLazyImports:
assert callable(agents.sync_spec_to_source)
def test_lazy_import_constants_from_base(self):
"""Test lazy import of constants from agents.base."""
"""Test that constants from agents.base are accessible.
Note: agents.base is explicitly imported for CodeQL compliance,
so it's already in sys.modules when agents is imported.
"""
import agents
# Constants should not be imported until accessed
assert "agents.base" not in sys.modules
# Access first constant
delay = agents.AUTO_CONTINUE_DELAY_SECONDS
# agents.base is imported explicitly for CodeQL compliance
assert "agents.base" in sys.modules
# Constants should still be accessible
delay = agents.AUTO_CONTINUE_DELAY_SECONDS
assert delay == 3
# Access second constant - module already loaded
# Access second constant
pause_file = agents.HUMAN_INTERVENTION_FILE
assert pause_file == "PAUSE"
+139
View File
@@ -103,6 +103,28 @@ def pytest_collection_modifyitems(session, config, items):
"""
import importlib
# Skip test functions that come from standalone test scripts
# These scripts are meant to be run directly, not as pytest tests
standalone_scripts = [
'integrations.graphiti.test_graphiti_memory',
'integrations.graphiti.test_ollama_embedding_memory',
]
items_to_skip = []
for item in items:
# Check if the test is defined in a standalone test script
if hasattr(item, 'obj') and hasattr(item.obj, '__module__'):
module_name = item.obj.__module__
if any(script in module_name for script in standalone_scripts):
# Also ensure the function is not part of a test class
# (classes in test_test_*.py files should still run)
if not hasattr(item, 'cls') or item.cls is None:
items_to_skip.append(item)
# Skip the collected items from standalone scripts
for item in items_to_skip:
items.remove(item)
# Replace any MagicMock modules with real ones before tests run
# This is needed because test_spec_pipeline.py mocks certain modules
# at import time, which affects other test files
@@ -951,3 +973,120 @@ def ensure_modules_not_mocked(request):
del sys.modules[key]
importlib.invalidate_caches()
# =============================================================================
# REVIEW STATE FIXTURES
# =============================================================================
@pytest.fixture
def review_spec_dir(tmp_path: Path) -> Generator[Path, None, None]:
"""Create a review spec directory with spec.md and implementation_plan.json for testing.
Args:
tmp_path: pytest's built-in temporary directory fixture
Yields:
Path: Path to review spec directory with spec.md and implementation_plan.json files
"""
import json
review_dir = tmp_path / "review_spec"
review_dir.mkdir(exist_ok=True)
# Create a spec.md file that tests may modify
spec_file = review_dir / "spec.md"
spec_file.write_text("# Test Spec\n\nInitial content.")
# Create an implementation_plan.json file that tests may modify
plan_file = review_dir / "implementation_plan.json"
plan_data = {
"feature": "Test Feature",
"workflow_type": "feature",
"phases": [
{
"phase": 1,
"name": "Test Phase",
"chunks": [
{"id": "chunk-1", "description": "Test chunk", "status": "pending"}
],
}
],
}
plan_file.write_text(json.dumps(plan_data, indent=2))
yield review_dir
# Cleanup is handled by tmp_path
@pytest.fixture
def approved_state() -> "ReviewState":
"""Return an approved ReviewState instance.
Returns:
ReviewState: Instance with approved=True, approved_by="test_user",
and other expected values matching test expectations
"""
from review import ReviewState
return ReviewState(
approved=True,
approved_by="test_user",
approved_at="2024-01-15T10:30:00",
spec_hash="abc123",
review_count=2,
feedback=["Looks good!", "Minor suggestion added."],
)
@pytest.fixture
def pending_state() -> "ReviewState":
"""Return a pending (not approved) ReviewState instance.
Returns:
ReviewState: Instance with approved=False, empty approval fields
"""
from review import ReviewState
return ReviewState(
approved=False,
approved_by="",
approved_at="",
spec_hash="",
review_count=0,
)
@pytest.fixture
def complete_spec_dir(tmp_path: Path) -> Generator[Path, None, None]:
"""Create a complete spec directory with spec.md and implementation_plan.json for testing.
This is an alias for review_spec_dir since they provide the same structure.
Args:
tmp_path: pytest's built-in temporary directory fixture
Yields:
Path: Path to complete spec directory with spec.md and implementation_plan.json files
"""
import json
complete_dir = tmp_path / "complete_spec"
complete_dir.mkdir(exist_ok=True)
# Create a spec.md file that tests may modify
spec_file = complete_dir / "spec.md"
spec_file.write_text("# Complete Spec\n\nThis is a complete spec for testing.")
# Create an implementation_plan.json file that tests may modify
plan_file = complete_dir / "implementation_plan.json"
plan_data = {
"feature": "Complete Test Feature",
"workflow_type": "feature",
"phases": [
{
"phase": 1,
"name": "Test Phase",
"chunks": [
{"id": "chunk-1", "description": "Test chunk", "status": "pending"}
],
}
],
}
plan_file.write_text(json.dumps(plan_data, indent=2))
yield complete_dir
# Cleanup is handled by tmp_path
@@ -10,6 +10,7 @@ import pytest
def _has_db():
"""Check if graphiti_core.driver.kuzu_driver can be imported (requires kuzu or real_ladybug)."""
try:
import kuzu # noqa: F401
return True
except ImportError:
return False
+2
View File
@@ -84,6 +84,8 @@ class TestImportIsolation:
def test_import_creates_submodule_entries(self, fresh_import_context):
"""Test that importing main creates submodule entries"""
# Import ui.main to trigger module loading
import ui.main
# Check that submodules are registered
assert "ui.main" in sys.modules