mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
Signed-off-by: Claude <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
This commit is contained in:
@@ -547,6 +547,11 @@ class SyncService:
|
||||
file_content = await self._read_file_async(file_path)
|
||||
file_contains_frontmatter = has_frontmatter(file_content)
|
||||
|
||||
# Get file timestamps for tracking modification times
|
||||
file_stats = self.file_service.file_stats(path)
|
||||
created = datetime.fromtimestamp(file_stats.st_ctime).astimezone()
|
||||
modified = datetime.fromtimestamp(file_stats.st_mtime).astimezone()
|
||||
|
||||
# entity markdown will always contain front matter, so it can be used up create/update the entity
|
||||
entity_markdown = await self.entity_parser.parse_file(path)
|
||||
|
||||
@@ -585,8 +590,11 @@ class SyncService:
|
||||
# after relation processing is complete
|
||||
final_checksum = await self._compute_checksum_async(path)
|
||||
|
||||
# set checksum
|
||||
await self.entity_repository.update(entity.id, {"checksum": final_checksum})
|
||||
# Update checksum and timestamps from file system
|
||||
# This ensures temporal ordering in search and recent activity uses actual file modification times
|
||||
await self.entity_repository.update(
|
||||
entity.id, {"checksum": final_checksum, "created_at": created, "updated_at": modified}
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
f"Markdown sync completed: path={path}, entity_id={entity.id}, "
|
||||
@@ -659,13 +667,18 @@ class SyncService:
|
||||
# Re-raise if it's a different integrity error
|
||||
raise
|
||||
else:
|
||||
# Get file timestamps for updating modification time
|
||||
file_stats = self.file_service.file_stats(path)
|
||||
modified = datetime.fromtimestamp(file_stats.st_mtime).astimezone()
|
||||
|
||||
entity = await self.entity_repository.get_by_file_path(path)
|
||||
if entity is None: # pragma: no cover
|
||||
logger.error(f"Entity not found for existing file, path={path}")
|
||||
raise ValueError(f"Entity not found for existing file: {path}")
|
||||
|
||||
# Update checksum and modification time from file system
|
||||
updated = await self.entity_repository.update(
|
||||
entity.id, {"file_path": path, "checksum": checksum}
|
||||
entity.id, {"file_path": path, "checksum": checksum, "updated_at": modified}
|
||||
)
|
||||
|
||||
if updated is None: # pragma: no cover
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Tests for entity parser error handling (issues #184 and #185)."""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
|
||||
from basic_memory.markdown.entity_parser import EntityParser
|
||||
@@ -213,4 +212,4 @@ async def test_parse_valid_file_still_works(tmp_path):
|
||||
assert result is not None
|
||||
assert result.frontmatter.title == "Valid File"
|
||||
assert result.frontmatter.type == "knowledge"
|
||||
assert result.frontmatter.tags == ["test", "valid"]
|
||||
assert result.frontmatter.tags == ["test", "valid"]
|
||||
|
||||
@@ -645,6 +645,90 @@ Testing file timestamps
|
||||
assert abs(entity_updated_epoch - file_stats.st_mtime) < tolerance # Allow tolerance difference
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sync_updates_timestamps_on_file_modification(
|
||||
sync_service: SyncService,
|
||||
project_config: ProjectConfig,
|
||||
entity_service: EntityService,
|
||||
):
|
||||
"""Test that sync updates entity timestamps when files are modified.
|
||||
|
||||
This test specifically validates that when an existing file is modified and re-synced,
|
||||
the updated_at timestamp in the database reflects the file's actual modification time,
|
||||
not the database operation time. This is critical for accurate temporal ordering in
|
||||
search and recent_activity queries.
|
||||
"""
|
||||
import time
|
||||
|
||||
project_dir = project_config.home
|
||||
|
||||
# Create initial file
|
||||
initial_content = """
|
||||
---
|
||||
type: knowledge
|
||||
---
|
||||
# Test File
|
||||
Initial content for timestamp test
|
||||
"""
|
||||
file_path = project_dir / "timestamp_test.md"
|
||||
await create_test_file(file_path, initial_content)
|
||||
|
||||
# Initial sync
|
||||
await sync_service.sync(project_config.home)
|
||||
|
||||
# Get initial entity and timestamps
|
||||
entity_before = await entity_service.get_by_permalink("timestamp-test")
|
||||
initial_updated_at = entity_before.updated_at
|
||||
|
||||
# Wait a bit to ensure filesystem timestamp changes
|
||||
time.sleep(0.1)
|
||||
|
||||
# Modify the file content
|
||||
modified_content = """
|
||||
---
|
||||
type: knowledge
|
||||
---
|
||||
# Test File
|
||||
Modified content for timestamp test
|
||||
|
||||
## Observations
|
||||
- [test] This was modified
|
||||
"""
|
||||
file_path.write_text(modified_content)
|
||||
|
||||
# Wait to ensure mtime is different
|
||||
time.sleep(0.1)
|
||||
|
||||
# Get the file's modification time after our changes
|
||||
file_stats_after_modification = file_path.stat()
|
||||
|
||||
# Re-sync the modified file
|
||||
await sync_service.sync(project_config.home)
|
||||
|
||||
# Get entity after re-sync
|
||||
entity_after = await entity_service.get_by_permalink("timestamp-test")
|
||||
|
||||
# Verify that updated_at changed
|
||||
assert entity_after.updated_at != initial_updated_at, (
|
||||
"updated_at should change when file is modified"
|
||||
)
|
||||
|
||||
# Verify that updated_at matches the file's modification time, not db operation time
|
||||
entity_updated_epoch = entity_after.updated_at.timestamp()
|
||||
file_mtime = file_stats_after_modification.st_mtime
|
||||
|
||||
# Allow 2s difference on Windows due to filesystem timing precision
|
||||
tolerance = 2 if os.name == "nt" else 1
|
||||
assert abs(entity_updated_epoch - file_mtime) < tolerance, (
|
||||
f"Entity updated_at ({entity_after.updated_at}) should match file mtime "
|
||||
f"({datetime.fromtimestamp(file_mtime)}) within {tolerance}s tolerance"
|
||||
)
|
||||
|
||||
# Verify the content was actually updated
|
||||
assert len(entity_after.observations) == 1
|
||||
assert entity_after.observations[0].content == "This was modified"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_file_move_updates_search_index(
|
||||
sync_service: SyncService,
|
||||
|
||||
Reference in New Issue
Block a user