mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
181 lines
5.4 KiB
Python
181 lines
5.4 KiB
Python
"""Tests for FileSyncService."""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import pytest_asyncio
|
|
|
|
from basic_memory.services.file_sync_service import FileSyncService, SyncError
|
|
|
|
|
|
@pytest_asyncio.fixture
|
|
async def file_sync_service(document_repository) -> FileSyncService:
|
|
"""Create FileSyncService instance."""
|
|
return FileSyncService(document_repository)
|
|
|
|
|
|
@pytest_asyncio.fixture
|
|
async def docs_dir(test_project_path) -> Path:
|
|
"""Get documents directory."""
|
|
return test_project_path / "documents"
|
|
|
|
|
|
@pytest_asyncio.fixture
|
|
async def sample_files(docs_dir) -> dict[str, str]:
|
|
"""Create some sample test files."""
|
|
# Create test structure
|
|
design_dir = docs_dir / "design"
|
|
notes_dir = docs_dir / "notes"
|
|
design_dir.mkdir(exist_ok=True)
|
|
notes_dir.mkdir(exist_ok=True)
|
|
|
|
# Map of relative paths to content
|
|
files = {
|
|
"design/architecture.md": "# Architecture\nSome design notes",
|
|
"notes/meeting.md": "# Meeting Notes\nDiscussion points",
|
|
"README.md": "# Project\nOverview doc",
|
|
}
|
|
|
|
# Create files with full paths
|
|
for rel_path, content in files.items():
|
|
full_path = docs_dir / rel_path
|
|
full_path.write_text(content)
|
|
|
|
return files
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_scan_files(file_sync_service, docs_dir, sample_files):
|
|
"""Test scanning directory for files."""
|
|
scanned = await file_sync_service.scan_files(docs_dir)
|
|
|
|
# Should find all files
|
|
assert len(scanned) == len(sample_files)
|
|
|
|
# Paths should be relative and match sample files
|
|
assert set(scanned.keys()) == set(sample_files.keys())
|
|
|
|
# All files should have checksums
|
|
assert all(isinstance(checksum, str) for checksum in scanned.values())
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_find_new_files(file_sync_service, docs_dir, sample_files):
|
|
"""Test detecting new files."""
|
|
changes = await file_sync_service.find_changes(
|
|
await file_sync_service.scan_files(docs_dir)
|
|
)
|
|
|
|
# All files should be new
|
|
assert len(changes.new) == len(sample_files)
|
|
assert len(changes.modified) == 0
|
|
assert len(changes.deleted) == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_find_modified_files(file_sync_service, docs_dir, sample_files):
|
|
"""Test detecting modified files."""
|
|
# First sync to create DB records
|
|
await file_sync_service.sync(docs_dir)
|
|
|
|
# Modify a file
|
|
mod_path = docs_dir / "design/architecture.md"
|
|
mod_path.write_text("# Updated Architecture")
|
|
|
|
# Check changes
|
|
changes = await file_sync_service.find_changes(
|
|
await file_sync_service.scan_files(docs_dir)
|
|
)
|
|
|
|
assert len(changes.modified) == 1
|
|
assert "design/architecture.md" in changes.modified
|
|
assert len(changes.new) == 0
|
|
assert len(changes.deleted) == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_find_deleted_files(file_sync_service, docs_dir, sample_files):
|
|
"""Test detecting deleted files."""
|
|
# First sync to create DB records
|
|
await file_sync_service.sync(docs_dir)
|
|
|
|
# Delete a file
|
|
del_path = docs_dir / "notes/meeting.md"
|
|
del_path.unlink()
|
|
|
|
# Check changes
|
|
changes = await file_sync_service.find_changes(
|
|
await file_sync_service.scan_files(docs_dir)
|
|
)
|
|
|
|
assert len(changes.deleted) == 1
|
|
assert "notes/meeting.md" in changes.deleted
|
|
assert len(changes.new) == 0
|
|
assert len(changes.modified) == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_full_sync_process(file_sync_service, docs_dir, sample_files):
|
|
"""Test full sync process with various changes."""
|
|
# First sync to create initial state
|
|
initial_sync = await file_sync_service.sync(docs_dir)
|
|
assert initial_sync.total_changes == len(sample_files)
|
|
|
|
# Make some changes:
|
|
# 1. Add new file
|
|
(docs_dir / "notes").mkdir(exist_ok=True) # Ensure parent exists
|
|
(docs_dir / "notes/todo.md").write_text("# TODO\n- First item")
|
|
|
|
# 2. Modify existing file
|
|
(docs_dir / "README.md").write_text("# Updated Project")
|
|
|
|
# 3. Delete a file
|
|
(docs_dir / "design/architecture.md").unlink()
|
|
|
|
# Run sync
|
|
changes = await file_sync_service.sync(docs_dir)
|
|
|
|
# Verify changes
|
|
assert len(changes.new) == 1
|
|
assert "notes/todo.md" in changes.new
|
|
|
|
assert len(changes.modified) == 1
|
|
assert "README.md" in changes.modified
|
|
|
|
assert len(changes.deleted) == 1
|
|
assert "design/architecture.md" in changes.deleted
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_no_changes_sync(file_sync_service, docs_dir, sample_files):
|
|
"""Test sync when no changes are present."""
|
|
# First sync to create initial state
|
|
await file_sync_service.sync(docs_dir)
|
|
|
|
# Sync again immediately
|
|
changes = await file_sync_service.sync(docs_dir)
|
|
|
|
# Should detect no changes
|
|
assert changes.total_changes == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_sync_empty_directory(file_sync_service, docs_dir):
|
|
"""Test syncing an empty directory."""
|
|
changes = await file_sync_service.sync(docs_dir)
|
|
assert changes.total_changes == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_error_on_unreadable_file(file_sync_service, docs_dir):
|
|
"""Test handling of unreadable files during sync."""
|
|
# Create file without read permissions
|
|
bad_file = docs_dir / "bad.md"
|
|
bad_file.write_text("test")
|
|
bad_file.chmod(0o000) # Remove all permissions
|
|
|
|
with pytest.raises(SyncError):
|
|
await file_sync_service.sync(docs_dir)
|
|
|
|
# Clean up
|
|
bad_file.chmod(0o666) # Make readable/writable for cleanup |