mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
Addressed issues when running basic-memory on the Windows platform (#252)
Signed-off-by: Manuel Bliemel <manuel.bliemel@gmail.com>
This commit is contained in:
@@ -74,7 +74,7 @@ def add_project(
|
||||
) -> None:
|
||||
"""Add a new project."""
|
||||
# Resolve to absolute path
|
||||
resolved_path = os.path.abspath(os.path.expanduser(path))
|
||||
resolved_path = Path(os.path.abspath(os.path.expanduser(path))).as_posix()
|
||||
|
||||
try:
|
||||
data = {"name": name, "path": resolved_path, "set_default": set_default}
|
||||
@@ -156,7 +156,7 @@ def move_project(
|
||||
) -> None:
|
||||
"""Move a project to a new location."""
|
||||
# Resolve to absolute path
|
||||
resolved_path = os.path.abspath(os.path.expanduser(new_path))
|
||||
resolved_path = Path(os.path.abspath(os.path.expanduser(new_path))).as_posix()
|
||||
|
||||
try:
|
||||
data = {"path": resolved_path}
|
||||
|
||||
@@ -46,7 +46,7 @@ class BasicMemoryConfig(BaseSettings):
|
||||
|
||||
projects: Dict[str, str] = Field(
|
||||
default_factory=lambda: {
|
||||
"main": str(Path(os.getenv("BASIC_MEMORY_HOME", Path.home() / "basic-memory")))
|
||||
"main": Path(os.getenv("BASIC_MEMORY_HOME", Path.home() / "basic-memory")).as_posix()
|
||||
},
|
||||
description="Mapping of project names to their filesystem paths",
|
||||
)
|
||||
@@ -100,9 +100,9 @@ class BasicMemoryConfig(BaseSettings):
|
||||
"""Ensure configuration is valid after initialization."""
|
||||
# Ensure main project exists
|
||||
if "main" not in self.projects: # pragma: no cover
|
||||
self.projects["main"] = str(
|
||||
self.projects["main"] = (
|
||||
Path(os.getenv("BASIC_MEMORY_HOME", Path.home() / "basic-memory"))
|
||||
)
|
||||
).as_posix()
|
||||
|
||||
# Ensure default project is valid
|
||||
if self.default_project not in self.projects: # pragma: no cover
|
||||
@@ -215,7 +215,7 @@ class ConfigManager:
|
||||
|
||||
# Load config, modify it, and save it
|
||||
config = self.load_config()
|
||||
config.projects[name] = str(project_path)
|
||||
config.projects[name] = project_path.as_posix()
|
||||
self.save_config(config)
|
||||
return ProjectConfig(name=name, home=project_path)
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ def entity_model_from_markdown(
|
||||
# Only update permalink if it exists in frontmatter, otherwise preserve existing
|
||||
if markdown.frontmatter.permalink is not None:
|
||||
model.permalink = markdown.frontmatter.permalink
|
||||
model.file_path = str(file_path)
|
||||
model.file_path = file_path.as_posix()
|
||||
model.content_type = "text/markdown"
|
||||
model.created_at = markdown.created
|
||||
model.updated_at = markdown.modified
|
||||
|
||||
@@ -57,7 +57,7 @@ class EntityRepository(Repository[Entity]):
|
||||
"""
|
||||
query = (
|
||||
self.select()
|
||||
.where(Entity.file_path == str(file_path))
|
||||
.where(Entity.file_path == Path(file_path).as_posix())
|
||||
.options(*self.get_load_options())
|
||||
)
|
||||
return await self.find_one(query)
|
||||
@@ -68,7 +68,7 @@ class EntityRepository(Repository[Entity]):
|
||||
Args:
|
||||
file_path: Path to the entity file (will be converted to string internally)
|
||||
"""
|
||||
return await self.delete_by_fields(file_path=str(file_path))
|
||||
return await self.delete_by_fields(file_path=Path(file_path).as_posix())
|
||||
|
||||
def get_load_options(self) -> List[LoaderOption]:
|
||||
"""Get SQLAlchemy loader options for eager loading relationships."""
|
||||
|
||||
@@ -46,7 +46,7 @@ class ProjectRepository(Repository[Project]):
|
||||
Args:
|
||||
path: Path to the project directory (will be converted to string internally)
|
||||
"""
|
||||
query = self.select().where(Project.path == str(path))
|
||||
query = self.select().where(Project.path == Path(path).as_posix())
|
||||
return await self.find_one(query)
|
||||
|
||||
async def get_default_project(self) -> Optional[Project]:
|
||||
|
||||
@@ -6,6 +6,7 @@ import time
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
from pathlib import Path
|
||||
|
||||
from loguru import logger
|
||||
from sqlalchemy import Executable, Result, text
|
||||
@@ -59,8 +60,11 @@ class SearchIndexRow:
|
||||
if not self.type == SearchItemType.ENTITY.value and not self.file_path:
|
||||
return ""
|
||||
|
||||
# Normalize path separators to handle both Windows (\) and Unix (/) paths
|
||||
normalized_path = Path(self.file_path).as_posix()
|
||||
|
||||
# Split the path by slashes
|
||||
parts = self.file_path.split("/")
|
||||
parts = normalized_path.split("/")
|
||||
|
||||
# If there's only one part (e.g., "README.md"), it's at the root
|
||||
if len(parts) <= 1:
|
||||
|
||||
@@ -91,7 +91,7 @@ class EntityService(BaseService[EntityModel]):
|
||||
|
||||
Enhanced to detect and handle character-related conflicts.
|
||||
"""
|
||||
file_path_str = str(file_path)
|
||||
file_path_str = Path(file_path).as_posix()
|
||||
|
||||
# Check for potential file path conflicts before resolving permalink
|
||||
conflicts = await self.detect_file_path_conflicts(file_path_str)
|
||||
@@ -119,7 +119,7 @@ class EntityService(BaseService[EntityModel]):
|
||||
if markdown and markdown.frontmatter.permalink:
|
||||
desired_permalink = markdown.frontmatter.permalink
|
||||
else:
|
||||
desired_permalink = generate_permalink(file_path)
|
||||
desired_permalink = generate_permalink(file_path_str)
|
||||
|
||||
# Make unique if needed - enhanced to handle character conflicts
|
||||
permalink = desired_permalink
|
||||
@@ -283,7 +283,7 @@ class EntityService(BaseService[EntityModel]):
|
||||
entity = await self.update_entity_and_observations(file_path, entity_markdown)
|
||||
|
||||
# add relations
|
||||
await self.update_entity_relations(str(file_path), entity_markdown)
|
||||
await self.update_entity_relations(file_path.as_posix(), entity_markdown)
|
||||
|
||||
# Set final checksum to match file
|
||||
entity = await self.repository.update(entity.id, {"checksum": checksum})
|
||||
@@ -374,7 +374,7 @@ class EntityService(BaseService[EntityModel]):
|
||||
"""
|
||||
logger.debug(f"Updating entity and observations: {file_path}")
|
||||
|
||||
db_entity = await self.repository.get_by_file_path(str(file_path))
|
||||
db_entity = await self.repository.get_by_file_path(file_path.as_posix())
|
||||
|
||||
# Clear observations for entity
|
||||
await self.observation_repository.delete_by_fields(entity_id=db_entity.id)
|
||||
@@ -498,7 +498,7 @@ class EntityService(BaseService[EntityModel]):
|
||||
|
||||
# Update entity and its relationships
|
||||
entity = await self.update_entity_and_observations(file_path, entity_markdown)
|
||||
await self.update_entity_relations(str(file_path), entity_markdown)
|
||||
await self.update_entity_relations(file_path.as_posix(), entity_markdown)
|
||||
|
||||
# Set final checksum to match file
|
||||
entity = await self.repository.update(entity.id, {"checksum": checksum})
|
||||
|
||||
@@ -100,7 +100,7 @@ class ProjectService:
|
||||
raise ValueError("Repository is required for add_project")
|
||||
|
||||
# Resolve to absolute path
|
||||
resolved_path = os.path.abspath(os.path.expanduser(path))
|
||||
resolved_path = Path(os.path.abspath(os.path.expanduser(path))).as_posix()
|
||||
|
||||
# First add to config file (this will validate the project doesn't exist)
|
||||
project_config = self.config_manager.add_project(name, resolved_path)
|
||||
@@ -323,7 +323,7 @@ class ProjectService:
|
||||
raise ValueError("Repository is required for move_project")
|
||||
|
||||
# Resolve to absolute path
|
||||
resolved_path = os.path.abspath(os.path.expanduser(new_path))
|
||||
resolved_path = Path(os.path.abspath(os.path.expanduser(new_path))).as_posix()
|
||||
|
||||
# Validate project exists in config
|
||||
if name not in self.config_manager.projects:
|
||||
@@ -378,7 +378,7 @@ class ProjectService:
|
||||
|
||||
# Update path if provided
|
||||
if updated_path:
|
||||
resolved_path = os.path.abspath(os.path.expanduser(updated_path))
|
||||
resolved_path = Path(os.path.abspath(os.path.expanduser(updated_path))).as_posix()
|
||||
|
||||
# Update in config
|
||||
config = self.config_manager.load_config()
|
||||
|
||||
@@ -619,7 +619,7 @@ class SyncService:
|
||||
continue
|
||||
|
||||
path = Path(root) / filename
|
||||
rel_path = str(path.relative_to(directory))
|
||||
rel_path = path.relative_to(directory).as_posix()
|
||||
checksum = await self.file_service.compute_checksum(rel_path)
|
||||
result.files[rel_path] = checksum
|
||||
result.checksums[checksum] = rel_path
|
||||
|
||||
@@ -197,7 +197,7 @@ class WatchService:
|
||||
|
||||
for change, path in changes:
|
||||
# convert to relative path
|
||||
relative_path = str(Path(path).relative_to(directory))
|
||||
relative_path = Path(path).relative_to(directory).as_posix()
|
||||
|
||||
# Skip .tmp files - they're temporary and shouldn't be synced
|
||||
if relative_path.endswith(".tmp"):
|
||||
|
||||
@@ -49,7 +49,7 @@ def generate_permalink(file_path: Union[Path, str, PathLike]) -> str:
|
||||
'中文/测试文档'
|
||||
"""
|
||||
# Convert Path to string if needed
|
||||
path_str = str(file_path)
|
||||
path_str = Path(str(file_path)).as_posix()
|
||||
|
||||
# Remove extension
|
||||
base = os.path.splitext(path_str)[0]
|
||||
@@ -220,6 +220,18 @@ def parse_tags(tags: Union[List[str], str, None]) -> List[str]:
|
||||
return []
|
||||
|
||||
|
||||
def normalize_newlines(multiline: str) -> str:
|
||||
"""Replace any \r\n, \r, or \n with the native newline.
|
||||
|
||||
Args:
|
||||
multiline: String containing any mixture of newlines.
|
||||
|
||||
Returns:
|
||||
A string with normalized newlines native to the platform.
|
||||
"""
|
||||
return re.sub(r'\r\n?|\n', os.linesep, multiline)
|
||||
|
||||
|
||||
def normalize_file_path_for_comparison(file_path: str) -> str:
|
||||
"""Normalize a file path for conflict detection.
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ from basic_memory.schemas import (
|
||||
EntityResponse,
|
||||
)
|
||||
from basic_memory.schemas.search import SearchItemType, SearchResponse
|
||||
from basic_memory.utils import normalize_newlines
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -684,14 +685,14 @@ async def test_edit_entity_prepend(client: AsyncClient, project_url):
|
||||
file_content = response.text
|
||||
|
||||
# Expected content with frontmatter preserved and content prepended to body
|
||||
expected_content = """---
|
||||
expected_content = normalize_newlines("""---
|
||||
title: Test Note
|
||||
type: note
|
||||
permalink: test/test-note
|
||||
---
|
||||
|
||||
Prepended content
|
||||
Original content"""
|
||||
Original content""")
|
||||
|
||||
assert file_content.strip() == expected_content.strip()
|
||||
|
||||
|
||||
@@ -168,8 +168,8 @@ async def test_update_project_path_endpoint(
|
||||
"""Test the update project endpoint for changing project path."""
|
||||
# Create a test project to update
|
||||
test_project_name = "test-update-project"
|
||||
old_path = str(tmp_path / "old-location")
|
||||
new_path = str(tmp_path / "new-location")
|
||||
old_path = (tmp_path / "old-location").as_posix()
|
||||
new_path = (tmp_path / "new-location").as_posix()
|
||||
|
||||
await project_service.add_project(test_project_name, old_path)
|
||||
|
||||
@@ -256,8 +256,8 @@ async def test_update_project_both_params_endpoint(
|
||||
"""Test the update project endpoint with both path and is_active parameters."""
|
||||
# Create a test project to update
|
||||
test_project_name = "test-update-both-project"
|
||||
old_path = str(tmp_path / "old-location")
|
||||
new_path = str(tmp_path / "new-location")
|
||||
old_path = (tmp_path / "old-location").as_posix()
|
||||
new_path = (tmp_path / "new-location").as_posix()
|
||||
|
||||
await project_service.add_project(test_project_name, old_path)
|
||||
|
||||
@@ -344,7 +344,8 @@ async def test_update_project_no_params_endpoint(test_config, client, project_se
|
||||
await project_service.add_project(test_project_name, test_path)
|
||||
proj_info = await project_service.get_project(test_project_name)
|
||||
assert proj_info.name == test_project_name
|
||||
assert proj_info.path == test_path
|
||||
# On Windows the path is prepended with a drive letter
|
||||
assert test_path in proj_info.path
|
||||
|
||||
try:
|
||||
# Try to update with no parameters
|
||||
@@ -354,7 +355,8 @@ async def test_update_project_no_params_endpoint(test_config, client, project_se
|
||||
assert response.status_code == 200
|
||||
proj_info = await project_service.get_project(test_project_name)
|
||||
assert proj_info.name == test_project_name
|
||||
assert proj_info.path == test_path
|
||||
# On Windows the path is prepended with a drive letter
|
||||
assert test_path in proj_info.path
|
||||
|
||||
finally:
|
||||
# Clean up
|
||||
|
||||
@@ -7,6 +7,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from basic_memory.schemas import EntityResponse
|
||||
from basic_memory.utils import normalize_newlines
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -35,7 +36,7 @@ async def test_get_resource_content(client, project_config, entity_repository, p
|
||||
response = await client.get(f"{project_url}/resource/{entity.permalink}")
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/markdown; charset=utf-8"
|
||||
assert response.text == content
|
||||
assert response.text == normalize_newlines(content)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -66,7 +67,7 @@ async def test_get_resource_pagination(client, project_config, entity_repository
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/markdown; charset=utf-8"
|
||||
assert response.text == content
|
||||
assert response.text == normalize_newlines(content)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -148,7 +149,7 @@ async def test_get_resource_observation(client, project_config, entity_repositor
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/markdown; charset=utf-8"
|
||||
assert (
|
||||
"""
|
||||
normalize_newlines("""
|
||||
---
|
||||
title: Test Entity
|
||||
type: test
|
||||
@@ -158,7 +159,7 @@ permalink: test/test-entity
|
||||
# Test Content
|
||||
|
||||
- [note] an observation.
|
||||
""".strip()
|
||||
""".strip())
|
||||
in response.text
|
||||
)
|
||||
|
||||
@@ -196,7 +197,7 @@ async def test_get_resource_entities(client, project_config, entity_repository,
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/markdown; charset=utf-8"
|
||||
assert (
|
||||
f"""
|
||||
normalize_newlines(f"""
|
||||
--- memory://test/test-entity {entity1.updated_at.isoformat()} {entity1.checksum[:8]}
|
||||
|
||||
# Test Content
|
||||
@@ -206,7 +207,7 @@ async def test_get_resource_entities(client, project_config, entity_repository,
|
||||
# Related Content
|
||||
- links to [[Test Entity]]
|
||||
|
||||
""".strip()
|
||||
""".strip())
|
||||
in response.text
|
||||
)
|
||||
|
||||
@@ -249,7 +250,7 @@ async def test_get_resource_entities_pagination(
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/markdown; charset=utf-8"
|
||||
assert (
|
||||
"""
|
||||
normalize_newlines("""
|
||||
---
|
||||
title: Related Entity
|
||||
type: test
|
||||
@@ -258,7 +259,7 @@ permalink: test/related-entity
|
||||
|
||||
# Related Content
|
||||
- links to [[Test Entity]]
|
||||
""".strip()
|
||||
""".strip())
|
||||
in response.text
|
||||
)
|
||||
|
||||
@@ -297,7 +298,7 @@ async def test_get_resource_relation(client, project_config, entity_repository,
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/markdown; charset=utf-8"
|
||||
assert (
|
||||
f"""
|
||||
normalize_newlines(f"""
|
||||
--- memory://test/test-entity {entity1.updated_at.isoformat()} {entity1.checksum[:8]}
|
||||
|
||||
# Test Content
|
||||
@@ -307,7 +308,7 @@ async def test_get_resource_relation(client, project_config, entity_repository,
|
||||
# Related Content
|
||||
- links to [[Test Entity]]
|
||||
|
||||
""".strip()
|
||||
""".strip())
|
||||
in response.text
|
||||
)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import pytest_asyncio
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from basic_memory.schemas.search import SearchResponse, SearchItemType
|
||||
from basic_memory.utils import normalize_newlines
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
@@ -61,7 +62,7 @@ async def test_note_unicode_content(app):
|
||||
|
||||
# Read back should preserve unicode
|
||||
result = await read_note.fn("test/unicode-test")
|
||||
assert content in result
|
||||
assert normalize_newlines(content) in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -4,6 +4,7 @@ from textwrap import dedent
|
||||
import pytest
|
||||
|
||||
from basic_memory.mcp.tools import write_note, read_note, delete_note
|
||||
from basic_memory.utils import normalize_newlines
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -33,7 +34,7 @@ async def test_write_note(app):
|
||||
# Try reading it back via permalink
|
||||
content = await read_note.fn("test/test-note")
|
||||
assert (
|
||||
dedent("""
|
||||
normalize_newlines(dedent("""
|
||||
---
|
||||
title: Test Note
|
||||
type: note
|
||||
@@ -45,7 +46,7 @@ async def test_write_note(app):
|
||||
|
||||
# Test
|
||||
This is a test note
|
||||
""").strip()
|
||||
""").strip())
|
||||
in content
|
||||
)
|
||||
|
||||
@@ -62,7 +63,7 @@ async def test_write_note_no_tags(app):
|
||||
# Should be able to read it back
|
||||
content = await read_note.fn("test/simple-note")
|
||||
assert (
|
||||
dedent("""
|
||||
normalize_newlines(dedent("""
|
||||
---
|
||||
title: Simple Note
|
||||
type: note
|
||||
@@ -70,7 +71,7 @@ async def test_write_note_no_tags(app):
|
||||
---
|
||||
|
||||
Just some text
|
||||
""").strip()
|
||||
""").strip())
|
||||
in content
|
||||
)
|
||||
|
||||
@@ -114,7 +115,7 @@ async def test_write_note_update_existing(app):
|
||||
# Try reading it back
|
||||
content = await read_note.fn("test/test-note")
|
||||
assert (
|
||||
dedent(
|
||||
normalize_newlines(dedent(
|
||||
"""
|
||||
---
|
||||
title: Test Note
|
||||
@@ -128,7 +129,7 @@ async def test_write_note_update_existing(app):
|
||||
# Test
|
||||
This is an updated note
|
||||
"""
|
||||
).strip()
|
||||
).strip())
|
||||
== content
|
||||
)
|
||||
|
||||
@@ -393,7 +394,7 @@ async def test_write_note_preserves_content_frontmatter(app):
|
||||
# Try reading it back via permalink
|
||||
content = await read_note.fn("test/test-note")
|
||||
assert (
|
||||
dedent(
|
||||
normalize_newlines(dedent(
|
||||
"""
|
||||
---
|
||||
title: Test Note
|
||||
@@ -410,7 +411,7 @@ async def test_write_note_preserves_content_frontmatter(app):
|
||||
|
||||
This is a test note
|
||||
"""
|
||||
).strip()
|
||||
).strip())
|
||||
in content
|
||||
)
|
||||
|
||||
@@ -497,7 +498,7 @@ async def test_write_note_with_custom_entity_type(app):
|
||||
# Verify the entity type is correctly set in the frontmatter
|
||||
content = await read_note.fn("guides/test-guide")
|
||||
assert (
|
||||
dedent("""
|
||||
normalize_newlines(dedent("""
|
||||
---
|
||||
title: Test Guide
|
||||
type: guide
|
||||
@@ -509,7 +510,7 @@ async def test_write_note_with_custom_entity_type(app):
|
||||
|
||||
# Guide Content
|
||||
This is a guide
|
||||
""").strip()
|
||||
""").strip())
|
||||
in content
|
||||
)
|
||||
|
||||
|
||||
@@ -592,7 +592,7 @@ async def test_create_with_no_frontmatter(
|
||||
created = await entity_service.create_entity_from_markdown(file_path, entity_markdown)
|
||||
file_content, _ = await file_service.read_file(created.file_path)
|
||||
|
||||
assert str(file_path) == str(created.file_path)
|
||||
assert file_path.as_posix() == created.file_path
|
||||
assert created.title == "Git Workflow Guide"
|
||||
assert created.entity_type == "note"
|
||||
assert created.permalink is None
|
||||
@@ -898,7 +898,7 @@ async def test_create_entity_from_markdown_with_upsert(
|
||||
# Verify it created the entity successfully using the UPSERT approach
|
||||
assert result is not None
|
||||
assert result.title == "UPSERT Test"
|
||||
assert result.file_path == str(file_path)
|
||||
assert result.file_path == file_path.as_posix()
|
||||
# create_entity_from_markdown sets checksum to None (incomplete sync)
|
||||
assert result.checksum is None
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Tests for ProjectService."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -73,7 +74,7 @@ async def test_project_operations_sync_methods(
|
||||
"""
|
||||
# Generate a unique project name for testing
|
||||
test_project_name = f"test-project-{os.urandom(4).hex()}"
|
||||
test_project_path = str(tmp_path / "test-project")
|
||||
test_project_path = (tmp_path / "test-project").as_posix()
|
||||
|
||||
# Make sure the test directory exists
|
||||
os.makedirs(test_project_path, exist_ok=True)
|
||||
@@ -167,7 +168,7 @@ async def test_get_project_info(project_service: ProjectService, test_graph, tes
|
||||
async def test_add_project_async(project_service: ProjectService, tmp_path):
|
||||
"""Test adding a project with the updated async method."""
|
||||
test_project_name = f"test-async-project-{os.urandom(4).hex()}"
|
||||
test_project_path = str(tmp_path / "test-async-project")
|
||||
test_project_path = (tmp_path / "test-async-project").as_posix()
|
||||
|
||||
# Make sure the test directory exists
|
||||
os.makedirs(test_project_path, exist_ok=True)
|
||||
@@ -243,7 +244,7 @@ async def test_set_default_project_async(project_service: ProjectService, tmp_pa
|
||||
async def test_get_project_method(project_service: ProjectService, tmp_path):
|
||||
"""Test the get_project method directly."""
|
||||
test_project_name = f"test-get-project-{os.urandom(4).hex()}"
|
||||
test_project_path = str(tmp_path / "test-get-project")
|
||||
test_project_path = (tmp_path / "test-get-project").as_posix()
|
||||
|
||||
# Make sure the test directory exists
|
||||
os.makedirs(test_project_path, exist_ok=True)
|
||||
@@ -539,8 +540,8 @@ async def test_synchronize_projects_normalizes_project_names(
|
||||
async def test_move_project(project_service: ProjectService, tmp_path):
|
||||
"""Test moving a project to a new location."""
|
||||
test_project_name = f"test-move-project-{os.urandom(4).hex()}"
|
||||
old_path = str(tmp_path / "old-location")
|
||||
new_path = str(tmp_path / "new-location")
|
||||
old_path = (tmp_path / "old-location").as_posix()
|
||||
new_path = (tmp_path / "new-location").as_posix()
|
||||
|
||||
# Create old directory
|
||||
os.makedirs(old_path, exist_ok=True)
|
||||
@@ -590,8 +591,8 @@ async def test_move_project_nonexistent(project_service: ProjectService, tmp_pat
|
||||
async def test_move_project_db_mismatch(project_service: ProjectService, tmp_path):
|
||||
"""Test moving a project that exists in config but not in database."""
|
||||
test_project_name = f"test-move-mismatch-{os.urandom(4).hex()}"
|
||||
old_path = str(tmp_path / "old-location")
|
||||
new_path = str(tmp_path / "new-location")
|
||||
old_path = (tmp_path / "old-location").as_posix()
|
||||
new_path = (tmp_path / "new-location").as_posix()
|
||||
|
||||
# Create directories
|
||||
os.makedirs(old_path, exist_ok=True)
|
||||
@@ -624,7 +625,7 @@ async def test_move_project_db_mismatch(project_service: ProjectService, tmp_pat
|
||||
async def test_move_project_expands_path(project_service: ProjectService, tmp_path):
|
||||
"""Test that move_project expands ~ and relative paths."""
|
||||
test_project_name = f"test-move-expand-{os.urandom(4).hex()}"
|
||||
old_path = str(tmp_path / "old-location")
|
||||
old_path = (tmp_path / "old-location").as_posix()
|
||||
|
||||
# Create old directory
|
||||
os.makedirs(old_path, exist_ok=True)
|
||||
@@ -635,7 +636,7 @@ async def test_move_project_expands_path(project_service: ProjectService, tmp_pa
|
||||
|
||||
# Use a relative path for the move
|
||||
relative_new_path = "./new-location"
|
||||
expected_absolute_path = os.path.abspath(relative_new_path)
|
||||
expected_absolute_path = Path(os.path.abspath(relative_new_path)).as_posix()
|
||||
|
||||
# Move project using relative path
|
||||
await project_service.move_project(test_project_name, relative_new_path)
|
||||
|
||||
@@ -48,7 +48,7 @@ async def test_add_project_to_config(project_service: ProjectService, tmp_path,
|
||||
"""Test adding a project to the config manager."""
|
||||
# Generate unique project name for testing
|
||||
test_project_name = f"config-project-{os.urandom(4).hex()}"
|
||||
test_path = str(tmp_path / "config-project")
|
||||
test_path = (tmp_path / "config-project").as_posix()
|
||||
|
||||
# Make sure directory exists
|
||||
os.makedirs(test_path, exist_ok=True)
|
||||
@@ -72,8 +72,8 @@ async def test_update_project_path(project_service: ProjectService, tmp_path, co
|
||||
"""Test updating a project's path."""
|
||||
# Create a test project
|
||||
test_project = f"path-update-test-project-{os.urandom(4).hex()}"
|
||||
original_path = str(tmp_path / "original-path")
|
||||
new_path = str(tmp_path / "new-path")
|
||||
original_path = (tmp_path / "original-path").as_posix()
|
||||
new_path = (tmp_path / "new-path").as_posix()
|
||||
|
||||
# Make sure directories exist
|
||||
os.makedirs(original_path, exist_ok=True)
|
||||
|
||||
@@ -680,7 +680,7 @@ Content for move test
|
||||
# Check search index has updated path
|
||||
results = await search_service.search(SearchQuery(text="Content for move test"))
|
||||
assert len(results) == 1
|
||||
assert results[0].file_path == str(new_path.relative_to(project_dir))
|
||||
assert results[0].file_path == new_path.relative_to(project_dir).as_posix()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -14,12 +14,12 @@ class TestBasicMemoryConfig:
|
||||
config = BasicMemoryConfig()
|
||||
|
||||
# Should use the default path (home/basic-memory)
|
||||
expected_path = str(config_home / "basic-memory")
|
||||
expected_path = (config_home / "basic-memory").as_posix()
|
||||
assert config.projects["main"] == expected_path
|
||||
|
||||
def test_respects_basic_memory_home_environment_variable(self, config_home, monkeypatch):
|
||||
"""Test that config respects BASIC_MEMORY_HOME environment variable."""
|
||||
custom_path = str(config_home / "app" / "data")
|
||||
custom_path = (config_home / "app" / "data").as_posix()
|
||||
monkeypatch.setenv("BASIC_MEMORY_HOME", custom_path)
|
||||
|
||||
config = BasicMemoryConfig()
|
||||
@@ -46,11 +46,11 @@ class TestBasicMemoryConfig:
|
||||
monkeypatch.delenv("BASIC_MEMORY_HOME", raising=False)
|
||||
|
||||
# Create config without main project
|
||||
other_path = str(config_home / "some" / "path")
|
||||
other_path = (config_home / "some" / "path").as_posix()
|
||||
config = BasicMemoryConfig(projects={"other": other_path})
|
||||
|
||||
# model_post_init should have added main project with default path
|
||||
expected_path = str(config_home / "basic-memory")
|
||||
expected_path = (config_home / "basic-memory").as_posix()
|
||||
assert "main" in config.projects
|
||||
assert config.projects["main"] == expected_path
|
||||
|
||||
|
||||
Reference in New Issue
Block a user