From 1c6836d83f49bb981e154799635fd3603f192b42 Mon Sep 17 00:00:00 2001 From: phernandez Date: Tue, 7 Jan 2025 20:29:59 -0600 Subject: [PATCH] fix tests --- .../api/routers/knowledge_router.py | 61 +++--- src/basic_memory/markdown/knowledge_parser.py | 2 +- src/basic_memory/markdown/schemas.py | 2 +- src/basic_memory/models/knowledge.py | 2 +- src/basic_memory/schemas/base.py | 55 +++++- src/basic_memory/schemas/request.py | 2 +- src/basic_memory/schemas/response.py | 5 +- src/basic_memory/services/entity_service.py | 3 +- .../services/knowledge/entity_operations.py | 4 - .../services/knowledge/file_operations.py | 3 +- .../sync/knowledge_sync_service.py | 7 +- tests/api/test_discovery_router.py | 63 +++--- tests/api/test_knowledge_router.py | 181 ++++++++---------- tests/api/test_search_router.py | 119 ++++-------- tests/conftest.py | 13 +- tests/markdown/test_entity_parsing.py | 2 +- tests/markdown/test_knowledge_writer.py | 61 +++--- tests/markdown/test_note_writer.py | 29 ++- tests/mcp/conftest.py | 4 +- tests/mcp/test_tool_add_observations.py | 93 +++------ tests/mcp/test_tool_create_entities.py | 67 +++---- tests/mcp/test_tool_create_relations.py | 89 +++------ tests/mcp/test_tool_discovery.py | 6 +- tests/mcp/test_tool_get_entity.py | 49 ++--- tests/mcp/test_tool_open_nodes.py | 76 ++------ tests/repository/test_entity_repository.py | 86 +++++---- .../repository/test_observation_repository.py | 31 +-- tests/repository/test_relation_repository.py | 25 ++- tests/schemas/test_schemas.py | 27 +-- tests/services/test_activity_service.py | 0 tests/services/test_entity_service.py | 82 ++++---- tests/services/test_knowledge_service.py | 70 +++---- tests/services/test_relation_service.py | 13 +- tests/services/test_search_service.py | 5 +- tests/sync/test_knowledge_sync_service.py | 19 +- tests/sync/test_sync_knowledge.py | 6 +- tests/sync/test_sync_service.py | 14 +- 37 files changed, 596 insertions(+), 780 deletions(-) delete mode 100644 tests/services/test_activity_service.py diff --git a/src/basic_memory/api/routers/knowledge_router.py b/src/basic_memory/api/routers/knowledge_router.py index 041fe8d2..162dffdb 100644 --- a/src/basic_memory/api/routers/knowledge_router.py +++ b/src/basic_memory/api/routers/knowledge_router.py @@ -11,8 +11,6 @@ from basic_memory.deps import ( from basic_memory.schemas import ( CreateEntityRequest, EntityListResponse, - SearchNodesRequest, - SearchNodesResponse, CreateRelationsRequest, EntityResponse, AddObservationsRequest, @@ -23,7 +21,7 @@ from basic_memory.schemas import ( DeleteEntitiesRequest, UpdateEntityRequest, ) -from basic_memory.schemas.base import PathId, EntityType +from basic_memory.schemas.base import PathId from basic_memory.services.exceptions import EntityNotFoundError router = APIRouter(prefix="/knowledge", tags=["knowledge"]) @@ -36,15 +34,15 @@ async def create_entities( data: CreateEntityRequest, background_tasks: BackgroundTasks, knowledge_service: KnowledgeServiceDep, - search_service = Depends(get_search_service) + search_service=Depends(get_search_service), ) -> EntityListResponse: """Create new entities in the knowledge graph and index them.""" entities = await knowledge_service.create_entities(data.entities) - + # Index each entity for entity in entities: await search_service.index_entity(entity, background_tasks=background_tasks) - + return EntityListResponse( entities=[EntityResponse.model_validate(entity) for entity in entities] ) @@ -56,21 +54,21 @@ async def update_entity( data: UpdateEntityRequest, background_tasks: BackgroundTasks, knowledge_service: KnowledgeServiceDep, - search_service = Depends(get_search_service) + search_service=Depends(get_search_service), ) -> EntityResponse: """Update an existing entity and reindex it.""" try: # Convert request to dict, excluding None values update_data = data.model_dump(exclude_none=True) - + # Update the entity updated_entity = await knowledge_service.update_entity(path_id, **update_data) - + # Reindex since content changed await search_service.index_entity(updated_entity, background_tasks=background_tasks) - + return EntityResponse.model_validate(updated_entity) - + except EntityNotFoundError: raise HTTPException(status_code=404, detail=f"Entity with {path_id} not found") @@ -80,15 +78,15 @@ async def create_relations( data: CreateRelationsRequest, background_tasks: BackgroundTasks, knowledge_service: KnowledgeServiceDep, - search_service = Depends(get_search_service), + search_service=Depends(get_search_service), ) -> EntityListResponse: """Create relations between entities and update search index.""" updated_entities = await knowledge_service.create_relations(data.relations) - + # Reindex updated entities since relations have changed for entity in updated_entities: await search_service.index_entity(entity, background_tasks=background_tasks) - + return EntityListResponse( entities=[EntityResponse.model_validate(entity) for entity in updated_entities] ) @@ -99,17 +97,17 @@ async def add_observations( data: AddObservationsRequest, background_tasks: BackgroundTasks, knowledge_service: KnowledgeServiceDep, - search_service = Depends(get_search_service) + search_service=Depends(get_search_service), ) -> EntityResponse: """Add observations to an entity and update search index.""" logger.debug(f"Adding observations to entity: {data.path_id}") updated_entity = await knowledge_service.add_observations( data.path_id, data.observations, data.context ) - + # Reindex the entity with new observations await search_service.index_entity(updated_entity, background_tasks=background_tasks) - + return EntityResponse.model_validate(updated_entity) @@ -122,9 +120,9 @@ async def get_entity(path_id: PathId, knowledge_service: KnowledgeServiceDep) -> try: entity = await knowledge_service.get_entity_by_path_id(path_id) entity_response = EntityResponse.model_validate(entity) - + # if the entity is a note, we add the content via reading from the file - if entity_response.entity_type == EntityType.NOTE: + if entity_response.entity_type == "note": content = await knowledge_service.read_entity_content(entity) entity_response.content = content @@ -132,8 +130,11 @@ async def get_entity(path_id: PathId, knowledge_service: KnowledgeServiceDep) -> except EntityNotFoundError: raise HTTPException(status_code=404, detail=f"Entity with {path_id} not found") + @router.post("/nodes", response_model=EntityListResponse) -async def open_nodes(data: OpenNodesRequest, entity_service: EntityServiceDep) -> EntityListResponse: +async def open_nodes( + data: OpenNodesRequest, entity_service: EntityServiceDep +) -> EntityListResponse: """Open specific nodes by their names.""" entities = await entity_service.open_nodes(data.path_ids) return EntityListResponse( @@ -149,15 +150,15 @@ async def delete_entities( data: DeleteEntitiesRequest, background_tasks: BackgroundTasks, knowledge_service: KnowledgeServiceDep, - search_service = Depends(get_search_service) + search_service=Depends(get_search_service), ) -> DeleteEntitiesResponse: """Delete entities and remove from search index.""" deleted = await knowledge_service.delete_entities(data.path_ids) - + # Remove each deleted entity from search index for path_id in data.path_ids: background_tasks.add_task(search_service.delete_by_path_id, path_id) - + return DeleteEntitiesResponse(deleted=deleted) @@ -166,15 +167,15 @@ async def delete_observations( data: DeleteObservationsRequest, background_tasks: BackgroundTasks, knowledge_service: KnowledgeServiceDep, - search_service = Depends(get_search_service) + search_service=Depends(get_search_service), ) -> EntityResponse: """Delete observations and update search index.""" path_id = data.path_id updated_entity = await knowledge_service.delete_observations(path_id, data.observations) - + # Reindex the entity since observations changed await search_service.index_entity(updated_entity, background_tasks=background_tasks) - + return EntityResponse.model_validate(updated_entity) @@ -183,15 +184,15 @@ async def delete_relations( data: DeleteRelationsRequest, background_tasks: BackgroundTasks, knowledge_service: KnowledgeServiceDep, - search_service = Depends(get_search_service) + search_service=Depends(get_search_service), ) -> EntityListResponse: """Delete relations and update search index.""" updated_entities = await knowledge_service.delete_relations(data.relations) - + # Reindex entities since relations changed for entity in updated_entities: await search_service.index_entity(entity, background_tasks=background_tasks) - + return EntityListResponse( entities=[EntityResponse.model_validate(entity) for entity in updated_entities] - ) \ No newline at end of file + ) diff --git a/src/basic_memory/markdown/knowledge_parser.py b/src/basic_memory/markdown/knowledge_parser.py index 078bb0b3..6d8fcd4a 100644 --- a/src/basic_memory/markdown/knowledge_parser.py +++ b/src/basic_memory/markdown/knowledge_parser.py @@ -152,7 +152,7 @@ class KnowledgeParser(MarkdownParser[EntityMarkdown]): relations.append(relation) return EntityContent( - title=title, description=description, observations=observations, relations=relations + title=title, summary=description, observations=observations, relations=relations ) except ParseError: diff --git a/src/basic_memory/markdown/schemas.py b/src/basic_memory/markdown/schemas.py index 4427abd4..e069e340 100644 --- a/src/basic_memory/markdown/schemas.py +++ b/src/basic_memory/markdown/schemas.py @@ -37,7 +37,7 @@ class EntityContent(BaseModel): """Content sections of an entity markdown file.""" title: str - description: Optional[str] = None + summary: Optional[str] = None observations: List[Observation] = [] relations: List[Relation] = [] context: Optional[str] = None diff --git a/src/basic_memory/models/knowledge.py b/src/basic_memory/models/knowledge.py index 8ac9ed7c..e275c2dd 100644 --- a/src/basic_memory/models/knowledge.py +++ b/src/basic_memory/models/knowledge.py @@ -85,7 +85,7 @@ class Entity(Base): return self.incoming_relations + self.outgoing_relations def __repr__(self) -> str: - return f"Entity(id={self.id}, name='{self.name}', type='{self.entity_type}')" + return f"Entity(id={self.id}, name='{self.name}', type='{self.entity_type}', summary='{self.summary}')" class ObservationCategory(str, Enum): diff --git a/src/basic_memory/schemas/base.py b/src/basic_memory/schemas/base.py index 77aae01c..c84a63ea 100644 --- a/src/basic_memory/schemas/base.py +++ b/src/basic_memory/schemas/base.py @@ -31,13 +31,13 @@ Common Relation Types: - 'extends': Inheritance/extension - 'tested_by': Test coverage """ - +import mimetypes import re from enum import Enum from typing import List, Optional, Annotated, Dict from annotated_types import MinLen, MaxLen -from pydantic import BaseModel, BeforeValidator, Field +from pydantic import BaseModel, BeforeValidator, Field, model_validator, ValidationError def to_snake_case(name: str) -> str: @@ -122,15 +122,33 @@ Examples: - "Depends on SQLAlchemy for database operations" """ +EntityType = Annotated[str, BeforeValidator(to_snake_case), MinLen(1), MaxLen(200)] +"""Classification of entity (e.g., 'person', 'project', 'concept'). -class EntityType(str, Enum): - """Type of entity. +The type serves multiple purposes: +1. Enables filtering and querying +3. Provides context for relations - - knowledge: Contain information used in the semantic graph - - note: Free form information +Common types are listed in the module docstring. """ - KNOWLEDGE = "knowledge" - NOTE = "note" + +ALLOWED_CONTENT_TYPES = { + 'text/markdown', + 'text/plain', + 'application/pdf', + 'image/jpeg', + 'image/png', + 'image/svg+xml', +} + +ContentType = Annotated[ + str, + BeforeValidator(str.lower), + Field(pattern=r'^[\w\-\+\.]+/[\w\-\+\.]+$'), + Field(json_schema_extra={"examples": list(ALLOWED_CONTENT_TYPES)}) +] + + RelationType = Annotated[str, BeforeValidator(to_snake_case), MinLen(1), MaxLen(200)] """Type of relationship between entities. Always use active voice present tense. @@ -252,7 +270,11 @@ class Entity(BaseModel): entity_type: EntityType entity_metadata: Optional[Dict] = Field(default=None, description="Optional metadata") content: Optional[str] = None - description: Optional[str] = None + summary: Optional[str] = None + content_type: ContentType = Field( + description="MIME type of the content (e.g. text/markdown, image/jpeg)", + examples=["text/markdown", "image/jpeg"] + ) observations: List[Observation] = [] @property @@ -265,3 +287,18 @@ class Entity(BaseModel): def file_path(self): """Get the file path for this entity based on its path_id.""" return f"{self.path_id}.md" + + @model_validator(mode='before') + @classmethod + def infer_content_type(cls, data: Dict) -> Dict: + """Infer content_type from file_path if not provided.""" + if 'content_type' not in data: + # Get path from either file_path or construct from path_id + file_path = data.get('file_path') or f"{data.get('name')}.md" + + if not file_path: + raise ValidationError("Either file_path or name must be provided") + mime_type, _ = mimetypes.guess_type(file_path) + data['content_type'] = mime_type or 'text/plain' + + return data \ No newline at end of file diff --git a/src/basic_memory/schemas/request.py b/src/basic_memory/schemas/request.py index bd5e9523..fccfbc45 100644 --- a/src/basic_memory/schemas/request.py +++ b/src/basic_memory/schemas/request.py @@ -94,7 +94,7 @@ class UpdateEntityRequest(BaseModel): """Request to update an existing entity.""" name: Optional[str] = None entity_type: Optional[EntityType] = None - description: Optional[str] = None + summary: Optional[str] = None content: Optional[str] = None entity_metadata: Optional[Dict[str, Any]] = None diff --git a/src/basic_memory/schemas/response.py b/src/basic_memory/schemas/response.py index f5f8c0a1..aadfb344 100644 --- a/src/basic_memory/schemas/response.py +++ b/src/basic_memory/schemas/response.py @@ -16,7 +16,7 @@ from typing import List, Optional, Dict, Any from pydantic import BaseModel, ConfigDict, Field, AliasPath, AliasChoices -from basic_memory.schemas.base import Observation, Relation, PathId, Entity, EntityType +from basic_memory.schemas.base import Observation, Relation, PathId, Entity, EntityType, ContentType from basic_memory.schemas.request import ObservationCreate @@ -122,7 +122,8 @@ class EntityResponse(SQLAlchemyModel): name: str entity_type: EntityType entity_metadata: Optional[Dict] = None - description: Optional[str] = None + content_type: ContentType + summary: Optional[str] = None content: Optional[str] = None observations: List[ObservationResponse] = [] relations: List[RelationResponse] = [] diff --git a/src/basic_memory/services/entity_service.py b/src/basic_memory/services/entity_service.py index e180e209..f28b0342 100644 --- a/src/basic_memory/services/entity_service.py +++ b/src/basic_memory/services/entity_service.py @@ -18,7 +18,8 @@ def entity_model(entity: EntitySchema): entity_metadata=entity.entity_metadata, path_id=entity.path_id, file_path=entity.file_path, - description=entity.description, + summary=entity.summary, + content_type=entity.content_type, observations=[Observation(content=observation) for observation in entity.observations], ) return model diff --git a/src/basic_memory/services/knowledge/entity_operations.py b/src/basic_memory/services/knowledge/entity_operations.py index 4c57b8c8..b95c15bc 100644 --- a/src/basic_memory/services/knowledge/entity_operations.py +++ b/src/basic_memory/services/knowledge/entity_operations.py @@ -10,7 +10,6 @@ from basic_memory.schemas import Entity as EntitySchema from basic_memory.services.entity_service import EntityService from basic_memory.services.exceptions import EntityNotFoundError from .file_operations import FileOperations -from ...models.knowledge import EntityType class EntityOperations: @@ -41,9 +40,6 @@ class EntityOperations: """ logger.debug(f"Reading entity with path_id: {entity.path_id}") - - if entity.entity_type != EntityType.NOTE: - raise ValueError(f"Entity type {entity.entity_type} not supported") # For notes, read the actual file content file_path = self.file_operations.get_entity_path(entity) diff --git a/src/basic_memory/services/knowledge/file_operations.py b/src/basic_memory/services/knowledge/file_operations.py index 493032fb..25b6db6d 100644 --- a/src/basic_memory/services/knowledge/file_operations.py +++ b/src/basic_memory/services/knowledge/file_operations.py @@ -8,7 +8,6 @@ from loguru import logger from basic_memory.markdown.knowledge_writer import KnowledgeWriter from basic_memory.markdown.note_writer import NoteWriter from basic_memory.models import Entity as EntityModel -from basic_memory.models.knowledge import EntityType from basic_memory.services.entity_service import EntityService from basic_memory.services.exceptions import FileOperationError from basic_memory.services.file_service import FileService @@ -54,7 +53,7 @@ class FileOperations: entity = await self.entity_service.get_by_path_id(entity.path_id) # Select writer based on entity type - writer = self.note_writer if entity.entity_type == EntityType.NOTE else self.knowledge_writer + writer = self.note_writer if entity.entity_type == "note" else self.knowledge_writer # Get frontmatter and content frontmatter = await writer.format_frontmatter(entity) diff --git a/src/basic_memory/sync/knowledge_sync_service.py b/src/basic_memory/sync/knowledge_sync_service.py index 02611cbd..16649746 100644 --- a/src/basic_memory/sync/knowledge_sync_service.py +++ b/src/basic_memory/sync/knowledge_sync_service.py @@ -29,7 +29,8 @@ def entity_model_from_markdown(file_path: str, markdown: EntityMarkdown) -> Enti entity_type=markdown.frontmatter.type, path_id=markdown.frontmatter.id, file_path=file_path, - description=markdown.content.description, + content_type="text/markdown", + summary=markdown.content.summary, observations=[ Observation( content=obs.content, @@ -83,7 +84,7 @@ class KnowledgeSyncService: # Update fields from markdown db_entity.name = markdown.content.title db_entity.entity_type = markdown.frontmatter.type - db_entity.summary = markdown.content.description + db_entity.summary = markdown.content.summary # Clear and update observations await self.observation_service.delete_by_entity(db_entity.id) @@ -102,7 +103,7 @@ class KnowledgeSyncService: { "name": db_entity.name, "entity_type": db_entity.entity_type, - "description": db_entity.summary, + "summary": db_entity.summary, # Mark as incomplete "checksum": None, }, diff --git a/tests/api/test_discovery_router.py b/tests/api/test_discovery_router.py index 66fdba59..44149333 100644 --- a/tests/api/test_discovery_router.py +++ b/tests/api/test_discovery_router.py @@ -4,11 +4,10 @@ import pytest import pytest_asyncio from httpx import AsyncClient -from basic_memory.models.knowledge import Entity, Observation, EntityType +from basic_memory.models.knowledge import Entity, Observation from basic_memory.repository.entity_repository import EntityRepository from basic_memory.schemas import EntityTypeList, ObservationCategoryList, TypedEntityList - pytestmark = pytest.mark.asyncio @@ -18,50 +17,54 @@ async def test_entities(entity_repository: EntityRepository) -> list[Entity]: entities = [ Entity( name="Memory Service", - entity_type=EntityType.KNOWLEDGE, - description="Core memory service", + entity_type="test", + content_type="text/markdown", + summary="Core memory service", path_id="component/memory_service", file_path="component/memory_service.md", observations=[ Observation(category="tech", content="Using SQLite for storage"), Observation(category="design", content="Local-first architecture"), - ] + ], ), Entity( name="File Format", - entity_type=EntityType.KNOWLEDGE, - description="File format spec", + entity_type="test", + content_type="text/markdown", + summary="File format spec", path_id="spec/file_format", file_path="spec/file_format.md", observations=[ Observation(category="feature", content="Support for frontmatter"), Observation(category="tech", content="UTF-8 encoding"), - ] + ], ), Entity( name="Technical Decision", - entity_type=EntityType.KNOWLEDGE, - description="Architecture decision", + entity_type="test", + content_type="text/markdown", + summary="Architecture decision", path_id="decision/tech_choice", file_path="decision/tech_choice.md", observations=[ Observation(category="note", content="Team discussed options"), Observation(category="design", content="Selected for scalability"), - ] + ], ), # Add another technical component for sorting tests Entity( name="API Service", - entity_type=EntityType.KNOWLEDGE, - description="API layer", + entity_type="test", + content_type="text/markdown", + summary="API layer", path_id="component/api_service", file_path="component/api_service.md", observations=[ Observation(category="tech", content="FastAPI based"), - ] + ], ), ] - + created = await entity_repository.add_all(entities) return created @@ -71,18 +74,18 @@ async def test_get_entity_types(client: AsyncClient, test_entities): # Get types response = await client.get("/discovery/entity-types") assert response.status_code == 200 - + # Parse response data = EntityTypeList.model_validate(response.json()) - + # Should have types from test data assert len(data.types) > 0 - assert "knowledge" in data.types - + assert "test" in data.types + # Types should all be strings assert isinstance(data.types, list) assert all(isinstance(t, str) for t in data.types) - + # Types should be unique assert len(data.types) == len(set(data.types)) @@ -92,21 +95,21 @@ async def test_get_observation_categories(client: AsyncClient, test_entities): # Get categories response = await client.get("/discovery/observation-categories") assert response.status_code == 200 - + # Parse response data = ObservationCategoryList.model_validate(response.json()) - + # Should have categories from test data assert len(data.categories) > 0 assert "tech" in data.categories assert "design" in data.categories assert "feature" in data.categories assert "note" in data.categories - + # Categories should all be strings assert isinstance(data.categories, list) assert all(isinstance(c, str) for c in data.categories) - + # Categories should be unique assert len(data.categories) == len(set(data.categories)) @@ -114,17 +117,17 @@ async def test_get_observation_categories(client: AsyncClient, test_entities): async def test_list_entities_by_type(client: AsyncClient, test_entities): """Test listing entities by type.""" # List technical components - response = await client.get("/discovery/entities/knowledge") + response = await client.get("/discovery/entities/test") assert response.status_code == 200 - + # Parse response data = TypedEntityList.model_validate(response.json()) - + # Check response structure - assert data.entity_type == "knowledge" + assert data.entity_type == "test" assert len(data.entities) == 4 assert data.total == 4 - + # Verify content names = {e.name for e in data.entities} assert "Memory Service" in names @@ -152,7 +155,7 @@ async def test_list_entities_empty_type(client: AsyncClient, test_entities): """Test listing entities for a type that doesn't exist.""" response = await client.get("/discovery/entities/nonexistent_type") assert response.status_code == 200 - + data = TypedEntityList.model_validate(response.json()) assert data.entity_type == "nonexistent_type" assert len(data.entities) == 0 diff --git a/tests/api/test_knowledge_router.py b/tests/api/test_knowledge_router.py index c47094e8..477970f2 100644 --- a/tests/api/test_knowledge_router.py +++ b/tests/api/test_knowledge_router.py @@ -10,7 +10,7 @@ from basic_memory.schemas import ( EntityResponse, EntityListResponse, ObservationResponse, - RelationResponse, EntityType, + RelationResponse, ) from basic_memory.schemas.search import SearchItemType, SearchResponse @@ -18,7 +18,7 @@ from basic_memory.schemas.search import SearchItemType, SearchResponse async def create_entity(client) -> EntityResponse: data = { "name": "TestEntity", - "entity_type": EntityType.KNOWLEDGE, + "entity_type": "test", "observations": ["First observation", "Second observation"], } # Create an entity @@ -63,8 +63,8 @@ async def add_observations(client, path_id: str) -> List[ObservationResponse]: async def create_related_entities(client) -> List[RelationResponse]: # pyright: ignore [reportReturnType] # Create two entities to relate entities = [ - {"name": "SourceEntity", "entity_type": EntityType.KNOWLEDGE}, - {"name": "TargetEntity", "entity_type": EntityType.KNOWLEDGE}, + {"name": "SourceEntity", "entity_type": "test"}, + {"name": "TargetEntity", "entity_type": "test"}, ] create_response = await client.post("/knowledge/entities", json={"entities": entities}) created = create_response.json()["entities"] @@ -114,7 +114,7 @@ async def test_create_entities(client: AsyncClient): async def test_get_entity(client: AsyncClient): """Should retrieve an entity by path ID.""" # First create an entity - data = {"name": "TestEntity", "entity_type": EntityType.KNOWLEDGE} + data = {"name": "TestEntity", "entity_type": "test"} response = await client.post("/knowledge/entities", json={"entities": [data]}) assert response.status_code == 200 data = response.json() @@ -127,7 +127,7 @@ async def test_get_entity(client: AsyncClient): assert response.status_code == 200 entity = response.json() assert entity["name"] == "TestEntity" - assert entity["entity_type"] == EntityType.KNOWLEDGE + assert entity["entity_type"] == "test" assert entity["path_id"] == "test_entity" @@ -141,7 +141,7 @@ async def test_create_relations(client: AsyncClient): async def test_add_observations(client: AsyncClient): """Should add observations to an entity.""" # Create an entity first - data = {"name": "TestEntity", "entity_type": EntityType.KNOWLEDGE} + data = {"name": "TestEntity", "entity_type": "test"} response = await client.post("/knowledge/entities", json={"entities": [data]}) path_id = "test_entity" @@ -154,14 +154,13 @@ async def test_add_observations(client: AsyncClient): assert len(entity["observations"]) == 2 - @pytest.mark.asyncio async def test_open_nodes(client: AsyncClient): """Should open multiple nodes by path IDs.""" # Create a few entities with different names entities = [ - {"name": "AlphaTest", "entity_type": EntityType.KNOWLEDGE}, - {"name": "BetaTest", "entity_type": EntityType.KNOWLEDGE}, + {"name": "AlphaTest", "entity_type": "test"}, + {"name": "BetaTest", "entity_type": "test"}, ] await client.post("/knowledge/entities", json={"entities": entities}) @@ -177,7 +176,7 @@ async def test_open_nodes(client: AsyncClient): assert len(data["entities"]) == 1 entity = data["entities"][0] assert entity["name"] == "AlphaTest" - assert entity["entity_type"] == EntityType.KNOWLEDGE + assert entity["entity_type"] == "test" assert entity["path_id"] == "alpha_test" @@ -185,7 +184,7 @@ async def test_open_nodes(client: AsyncClient): async def test_delete_entity(client: AsyncClient): """Test DELETE /knowledge/entities with path ID.""" # Create test entity - entity_data = {"name": "TestEntity", "entity_type": EntityType.KNOWLEDGE} + entity_data = {"name": "TestEntity", "entity_type": "test"} await client.post("/knowledge/entities", json={"entities": [entity_data]}) # Test deletion @@ -206,8 +205,8 @@ async def test_delete_entity_bulk(client: AsyncClient): """Test bulk entity deletion using path IDs.""" # Create test entities entities = [ - {"name": "Entity1", "entity_type": EntityType.KNOWLEDGE}, - {"name": "Entity2", "entity_type": EntityType.KNOWLEDGE}, + {"name": "Entity1", "entity_type": "test"}, + {"name": "Entity2", "entity_type": "test"}, ] await client.post("/knowledge/entities", json={"entities": entities}) @@ -229,14 +228,12 @@ async def test_delete_entity_bulk(client: AsyncClient): async def test_delete_entity_with_observations(client, observation_repository): """Test cascading delete with observations.""" # Create test entity and add observations - entity_data = {"name": "TestEntity", "entity_type": EntityType.KNOWLEDGE} + entity_data = {"name": "TestEntity", "entity_type": "test"} await client.post("/knowledge/entities", json={"entities": [entity_data]}) await add_observations(client, "TestEntity") # Delete entity - response = await client.post( - "/knowledge/entities/delete", json={"path_ids": ["TestEntity"]} - ) + response = await client.post("/knowledge/entities/delete", json={"path_ids": ["TestEntity"]}) assert response.status_code == 200 assert response.json() == {"deleted": True} @@ -249,7 +246,7 @@ async def test_delete_entity_with_observations(client, observation_repository): async def test_delete_observations(client, observation_repository): """Test deleting specific observations.""" # Create entity and add observations - entity_data = {"name": "TestEntity", "entity_type": EntityType.KNOWLEDGE} + entity_data = {"name": "TestEntity", "entity_type": "test"} await client.post("/knowledge/entities", json={"entities": [entity_data]}) observations = await add_observations(client, "TestEntity") # adds 2 @@ -293,9 +290,7 @@ async def test_delete_relations(client, relation_repository): @pytest.mark.asyncio async def test_delete_nonexistent_entity(client: AsyncClient): """Test deleting a nonexistent entity by path ID.""" - response = await client.post( - "/knowledge/entities/delete", json={"path_ids": ["non_existent"]} - ) + response = await client.post("/knowledge/entities/delete", json={"path_ids": ["non_existent"]}) assert response.status_code == 200 assert response.json() == {"deleted": True} @@ -304,7 +299,7 @@ async def test_delete_nonexistent_entity(client: AsyncClient): async def test_delete_nonexistent_observations(client: AsyncClient): """Test deleting nonexistent observations.""" # Create test entity - entity_data = {"name": "TestEntity", "entity_type": EntityType.KNOWLEDGE} + entity_data = {"name": "TestEntity", "entity_type": "test"} await client.post("/knowledge/entities", json={"entities": [entity_data]}) request_data = {"path_id": "TestEntity", "observations": ["Nonexistent observation"]} @@ -336,21 +331,20 @@ async def test_delete_nonexistent_relations(client: AsyncClient): assert del_response.entities == [] - @pytest.mark.asyncio async def test_full_knowledge_flow(client: AsyncClient): """Test complete knowledge graph flow with path IDs.""" # 1. Create main entities main_entities = [ - {"name": "MainEntity", "entity_type": EntityType.KNOWLEDGE}, - {"name": "NonEntity", "entity_type": EntityType.KNOWLEDGE}, + {"name": "MainEntity", "entity_type": "test"}, + {"name": "NonEntity", "entity_type": "test"}, ] await client.post("/knowledge/entities", json={"entities": main_entities}) # 2. Create related entities related_entities = [ - {"name": "RelatedOne", "entity_type": EntityType.KNOWLEDGE}, - {"name": "RelatedTwo", "entity_type": EntityType.KNOWLEDGE}, + {"name": "RelatedOne", "entity_type": "test"}, + {"name": "RelatedTwo", "entity_type": "test"}, ] await client.post("/knowledge/entities", json={"entities": related_entities}) @@ -374,7 +368,7 @@ async def test_full_knowledge_flow(client: AsyncClient): ) assert relations_response.status_code == 200 relations_entities = relations_response.json() - assert len(relations_entities["entities"]) == 3 + assert len(relations_entities["entities"]) == 3 # 4. Add observations to main entity await client.post( @@ -402,9 +396,7 @@ async def test_full_knowledge_flow(client: AsyncClient): # 6. Search should find all related entities search = await client.post("/search/", json={"text": "Related"}) matches = search.json()["results"] - assert ( - len(matches) == 1 - ) + assert len(matches) == 1 # 7. Delete main entity response = await client.post( @@ -424,7 +416,7 @@ async def test_entity_indexing(client: AsyncClient): """Test entity creation includes search indexing.""" data = { "name": "SearchTest", - "entity_type": EntityType.KNOWLEDGE, + "entity_type": "test", "observations": ["Unique searchable observation"], } @@ -449,7 +441,7 @@ async def test_observation_update_indexing(client: AsyncClient): # Create entity data = { "name": "TestEntity", - "entity_type": EntityType.KNOWLEDGE, + "entity_type": "test", "observations": ["Initial observation"], } response = await client.post("/knowledge/entities", json={"entities": [data]}) @@ -479,7 +471,7 @@ async def test_entity_delete_indexing(client: AsyncClient): """Test deleted entities are removed from search index.""" data = { "name": "DeleteTest", - "entity_type": EntityType.KNOWLEDGE, + "entity_type": "test", "observations": ["Searchable observation that should be removed"], } @@ -514,8 +506,8 @@ async def test_relation_indexing(client: AsyncClient): """Test relations are included in search index.""" # Create entities entities = [ - {"name": "SourceTest", "entity_type": EntityType.KNOWLEDGE}, - {"name": "TargetTest", "entity_type": EntityType.KNOWLEDGE}, + {"name": "SourceTest", "entity_type": "test"}, + {"name": "TargetTest", "entity_type": "test"}, ] create_response = await client.post("/knowledge/entities", json={"entities": entities}) assert create_response.status_code == 200 @@ -541,7 +533,7 @@ async def test_relation_indexing(client: AsyncClient): "/search/", json={"text": "sphinx relation", "types": [SearchItemType.ENTITY.value]} ) search_result = SearchResponse.model_validate(search_response.json()) - assert len(search_result.results) == 2 # Both source and target entities + assert len(search_result.results) == 2 # Both source and target entities path_ids = {r.path_id for r in search_result.results} assert path_ids == {"source_test", "target_test"} @@ -552,9 +544,9 @@ async def test_update_entity_basic(client: AsyncClient): # Create initial entity data = { "name": "test", - "entity_type": EntityType.KNOWLEDGE, - "description": "Initial description", - "entity_metadata": {"status": "draft"} + "entity_type": "test", + "summary": "Initial description", + "entity_metadata": {"status": "draft"}, } response = await client.post("/knowledge/entities", json={"entities": [data]}) entity = response.json()["entities"][0] @@ -562,7 +554,7 @@ async def test_update_entity_basic(client: AsyncClient): # Update basic fields update_data = { "name": "updated-test", - "description": "Updated description", + "summary": "Updated description", } response = await client.put(f"/knowledge/entities/{entity['path_id']}", json=update_data) assert response.status_code == 200 @@ -570,7 +562,7 @@ async def test_update_entity_basic(client: AsyncClient): # Verify updates assert updated["name"] == "updated-test" - assert updated["description"] == "Updated description" + assert updated["summary"] == "Updated description" assert updated["entity_metadata"]["status"] == "draft" # Preserved @@ -578,19 +570,14 @@ async def test_update_entity_basic(client: AsyncClient): async def test_update_entity_content(client: AsyncClient): """Test updating content for different entity types.""" # Create a note entity - note_data = { - "name": "test-note", - "entity_type": EntityType.NOTE, - "description": "Test note" - } + note_data = {"name": "test-note", "entity_type": "note", "summary": "Test note"} response = await client.post("/knowledge/entities", json={"entities": [note_data]}) note = response.json()["entities"][0] # Update note content new_content = "# Updated Note\n\nNew content." response = await client.put( - f"/knowledge/entities/{note['path_id']}", - json={"content": new_content} + f"/knowledge/entities/{note['path_id']}", json={"content": new_content} ) assert response.status_code == 200 updated = response.json() @@ -608,49 +595,39 @@ async def test_update_entity_type_conversion(client: AsyncClient): # Create a note note_data = { "name": "test-note", - "entity_type": EntityType.NOTE, - "description": "Test note", - "content": "# Test Note\n\nInitial content." + "entity_type": "note", + "summary": "Test note", + "content": "# Test Note\n\nInitial content.", } response = await client.post("/knowledge/entities", json={"entities": [note_data]}) note = response.json()["entities"][0] # Convert to knowledge type response = await client.put( - f"/knowledge/entities/{note['path_id']}", - json={"entity_type": EntityType.KNOWLEDGE} + f"/knowledge/entities/{note['path_id']}", json={"entity_type": "test"} ) assert response.status_code == 200 updated = response.json() # Verify conversion - assert updated["entity_type"] == EntityType.KNOWLEDGE + assert updated["entity_type"] == "test" # Get latest to verify file format response = await client.get(f"/knowledge/entities/{updated['path_id']}") knowledge = response.json() - assert knowledge.get("content") is None + assert knowledge.get("content") is None @pytest.mark.asyncio async def test_update_entity_metadata(client: AsyncClient): """Test updating entity metadata.""" # Create entity - data = { - "name": "test", - "entity_type": EntityType.KNOWLEDGE, - "entity_metadata": {"status": "draft"} - } + data = {"name": "test", "entity_type": "test", "entity_metadata": {"status": "draft"}} response = await client.post("/knowledge/entities", json={"entities": [data]}) entity = response.json()["entities"][0] # Update metadata - update_data = { - "entity_metadata": { - "status": "final", - "reviewed": True - } - } + update_data = {"entity_metadata": {"status": "final", "reviewed": True}} response = await client.put(f"/knowledge/entities/{entity['path_id']}", json=update_data) assert response.status_code == 200 updated = response.json() @@ -663,10 +640,7 @@ async def test_update_entity_metadata(client: AsyncClient): @pytest.mark.asyncio async def test_update_entity_not_found(client: AsyncClient): """Test updating non-existent entity.""" - response = await client.put( - "/knowledge/entities/nonexistent", - json={"name": "new-name"} - ) + response = await client.put("/knowledge/entities/nonexistent", json={"name": "new-name"}) assert response.status_code == 404 @@ -674,59 +648,62 @@ async def test_update_entity_not_found(client: AsyncClient): async def test_update_entity_search_index(client: AsyncClient): """Test search index is updated after entity changes.""" # Create entity - data = { - "name": "test", - "entity_type": EntityType.KNOWLEDGE, - "description": "Initial searchable content" - } + data = {"name": "test", "entity_type": "test", "summary": "Initial searchable content"} response = await client.post("/knowledge/entities", json={"entities": [data]}) entity = response.json()["entities"][0] # Update with new searchable content - update_data = { - "description": "Updated with unique sphinx marker" - } + update_data = {"summary": "Updated with unique sphinx marker"} response = await client.put(f"/knowledge/entities/{entity['path_id']}", json=update_data) assert response.status_code == 200 # Search should find new content search_response = await client.post( - "/search/", - json={"text": "sphinx marker", "types": [SearchItemType.ENTITY.value]} + "/search/", json={"text": "sphinx marker", "types": [SearchItemType.ENTITY.value]} ) results = search_response.json()["results"] assert len(results) == 1 assert results[0]["path_id"] == entity["path_id"] - - + @pytest.mark.asyncio async def test_get_entity_with_relations(client: AsyncClient): """Test get response includes relations for both types.""" # Create a note and knowledge entity - note = await client.post("/knowledge/entities", json={"entities": [{ - "name": "test-note", - "entity_type": EntityType.NOTE, - "description": "Test note" - }]}) - knowledge = await client.post("/knowledge/entities", json={"entities": [{ - "name": "test-knowledge", - "entity_type": EntityType.KNOWLEDGE, - "description": "Test knowledge" - }]}) + note = await client.post( + "/knowledge/entities", + json={ + "entities": [{"name": "test-note", "entity_type": "note", "summary": "Test note"}] + }, + ) + knowledge = await client.post( + "/knowledge/entities", + json={ + "entities": [ + {"name": "test-knowledge", "entity_type": "test", "summary": "Test knowledge"} + ] + }, + ) # Add some relations between them - await client.post("/knowledge/relations", json={ - "relations": [{ - "from_id": note.json()["entities"][0]["path_id"], - "to_id": knowledge.json()["entities"][0]["path_id"], - "relation_type": "references" - }] - }) + await client.post( + "/knowledge/relations", + json={ + "relations": [ + { + "from_id": note.json()["entities"][0]["path_id"], + "to_id": knowledge.json()["entities"][0]["path_id"], + "relation_type": "references", + } + ] + }, + ) # Verify GET returns relations for both types note_response = await client.get(f"/knowledge/entities/{note.json()['entities'][0]['path_id']}") - knowledge_response = await client.get(f"/knowledge/entities/{knowledge.json()['entities'][0]['path_id']}") + knowledge_response = await client.get( + f"/knowledge/entities/{knowledge.json()['entities'][0]['path_id']}" + ) assert len(note_response.json()["relations"]) == 1 assert len(knowledge_response.json()["relations"]) == 1 diff --git a/tests/api/test_search_router.py b/tests/api/test_search_router.py index a6f3e93c..dec9a818 100644 --- a/tests/api/test_search_router.py +++ b/tests/api/test_search_router.py @@ -5,28 +5,30 @@ from datetime import datetime, timezone import pytest import pytest_asyncio from sqlalchemy import text + from basic_memory import db -from basic_memory.schemas import EntityType -from basic_memory.schemas.search import SearchQuery, SearchItemType, SearchResponse +from basic_memory.schemas.search import SearchItemType, SearchResponse @pytest.fixture def test_entity(): """Create a test entity.""" + class Entity: id = 1 name = "TestComponent" - entity_type = EntityType.KNOWLEDGE - entity_metadata = { "test": "test"} + entity_type = "test" + entity_metadata = {"test": "test"} path_id = "component/test_component" file_path = "entities/component/test_component.md" - description = "A test component for search testing" + summary = "A test component for search testing" + content_type = "text/markdown" created_at = datetime.now(timezone.utc) updated_at = datetime.now(timezone.utc) observations = [] relations = [] - return Entity() + return Entity() @pytest_asyncio.fixture @@ -36,16 +38,10 @@ async def indexed_entity(init_search_index, test_entity, search_service): return test_entity - @pytest.mark.asyncio async def test_search_basic(client, indexed_entity): """Test basic text search.""" - response = await client.post( - "/search/", - json={ - "text": "test component" - } - ) + response = await client.post("/search/", json={"text": "test component"}) assert response.status_code == 200 search_results = SearchResponse.model_validate(response.json()) assert len(search_results.results) == 1 @@ -57,23 +53,15 @@ async def test_search_with_type_filter(client, indexed_entity): """Test search with type filter.""" # Should find with correct type response = await client.post( - "/search/", - json={ - "text": "test", - "types": [SearchItemType.ENTITY.value] - } + "/search/", json={"text": "test", "types": [SearchItemType.ENTITY.value]} ) assert response.status_code == 200 search_results = SearchResponse.model_validate(response.json()) assert len(search_results.results) == 1 - + # Should not find with wrong type response = await client.post( - "/search/", - json={ - "text": "test", - "types": [SearchItemType.DOCUMENT.value] - } + "/search/", json={"text": "test", "types": [SearchItemType.DOCUMENT.value]} ) assert response.status_code == 200 search_results = SearchResponse.model_validate(response.json()) @@ -84,25 +72,13 @@ async def test_search_with_type_filter(client, indexed_entity): async def test_search_with_entity_type_filter(client, indexed_entity): """Test search with entity type filter.""" # Should find with correct entity type - response = await client.post( - "/search/", - json={ - "text": "test", - "entity_types": [EntityType.KNOWLEDGE] - } - ) + response = await client.post("/search/", json={"text": "test", "entity_types": ["test"]}) assert response.status_code == 200 search_results = SearchResponse.model_validate(response.json()) assert len(search_results.results) == 1 - + # Should not find with wrong entity type - response = await client.post( - "/search/", - json={ - "text": "test", - "entity_types": [EntityType.NOTE] - } - ) + response = await client.post("/search/", json={"text": "test", "entity_types": ["note"]}) assert response.status_code == 200 search_results = SearchResponse.model_validate(response.json()) assert len(search_results.results) == 0 @@ -114,24 +90,16 @@ async def test_search_with_date_filter(client, indexed_entity): # Should find with past date past_date = datetime(2020, 1, 1, tzinfo=timezone.utc) response = await client.post( - "/search/", - json={ - "text": "test", - "after_date": past_date.isoformat() - } + "/search/", json={"text": "test", "after_date": past_date.isoformat()} ) assert response.status_code == 200 search_results = SearchResponse.model_validate(response.json()) assert len(search_results.results) == 1 - + # Should not find with future date future_date = datetime(2030, 1, 1, tzinfo=timezone.utc) response = await client.post( - "/search/", - json={ - "text": "test", - "after_date": future_date.isoformat() - } + "/search/", json={"text": "test", "after_date": future_date.isoformat()} ) assert response.status_code == 200 search_results = SearchResponse.model_validate(response.json()) @@ -142,49 +110,34 @@ async def test_search_with_date_filter(client, indexed_entity): async def test_search_scoring(client, indexed_entity): """Test search result scoring.""" # Exact match should score higher - exact_response = await client.post( - "/search/", - json={"text": "TestComponent"} - ) - + exact_response = await client.post("/search/", json={"text": "TestComponent"}) + # Partial match should score lower - partial_response = await client.post( - "/search/", - json={"text": "test"} - ) - + partial_response = await client.post("/search/", json={"text": "test"}) + assert exact_response.status_code == 200 assert partial_response.status_code == 200 exact_result = SearchResponse.model_validate(exact_response.json()) partial_result = SearchResponse.model_validate(partial_response.json()) - + exact_score = exact_result.results[0].score partial_score = partial_result.results[0].score - + assert exact_score > partial_score @pytest.mark.asyncio async def test_search_empty(search_service, client): """Test search with no matches.""" - response = await client.post( - "/search/", - json={"text": "nonexistent"} - ) + response = await client.post("/search/", json={"text": "nonexistent"}) assert response.status_code == 200 search_result = SearchResponse.model_validate(response.json()) assert len(search_result.results) == 0 @pytest.mark.asyncio -async def test_reindex( - client, - search_service, - entity_service, - test_entity, - session_maker -): +async def test_reindex(client, search_service, entity_service, test_entity, session_maker): """Test reindex endpoint.""" # Create test entity and document await entity_service.create_entity(test_entity) @@ -195,10 +148,7 @@ async def test_reindex( await session.commit() # Verify nothing is searchable - response = await client.post( - "/search/", - json={"text": "test"} - ) + response = await client.post("/search/", json={"text": "test"}) search_results = SearchResponse.model_validate(response.json()) assert len(search_results.results) == 0 @@ -208,12 +158,9 @@ async def test_reindex( assert reindex_response.json()["status"] == "ok" # Verify content is searchable again - search_response = await client.post( - "/search/", - json={"text": "test"} - ) + search_response = await client.post("/search/", json={"text": "test"}) search_results = SearchResponse.model_validate(search_response.json()) - assert len(search_results.results) == 1 + assert len(search_results.results) == 1 @pytest.mark.asyncio @@ -224,9 +171,9 @@ async def test_multiple_filters(client, indexed_entity): json={ "text": "test", "types": [SearchItemType.ENTITY.value], - "entity_types": [EntityType.KNOWLEDGE], - "after_date": datetime(2020, 1, 1, tzinfo=timezone.utc).isoformat() - } + "entity_types": ["test"], + "after_date": datetime(2020, 1, 1, tzinfo=timezone.utc).isoformat(), + }, ) assert response.status_code == 200 search_result = SearchResponse.model_validate(response.json()) @@ -234,4 +181,4 @@ async def test_multiple_filters(client, indexed_entity): result = search_result.results[0] assert result.path_id == indexed_entity.path_id assert result.type == SearchItemType.ENTITY.value - assert result.metadata["entity_type"] == EntityType.KNOWLEDGE \ No newline at end of file + assert result.metadata["entity_type"] == "test" diff --git a/tests/conftest.py b/tests/conftest.py index 0cb70781..183a20a4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,7 +14,7 @@ from basic_memory.markdown.knowledge_parser import KnowledgeParser from basic_memory.markdown.knowledge_writer import KnowledgeWriter from basic_memory.markdown.note_writer import NoteWriter from basic_memory.models import Base -from basic_memory.models.knowledge import Entity, EntityType +from basic_memory.models.knowledge import Entity from basic_memory.repository.entity_repository import EntityRepository from basic_memory.repository.observation_repository import ObservationRepository from basic_memory.repository.relation_repository import RelationRepository @@ -74,7 +74,6 @@ async def session_maker(engine_factory) -> async_sessionmaker[AsyncSession]: return session_maker - @pytest_asyncio.fixture(scope="function") async def entity_repository(session_maker: async_sessionmaker[AsyncSession]) -> EntityRepository: """Create an EntityRepository instance.""" @@ -129,6 +128,7 @@ def knowledge_writer(): """Create writer instance.""" return KnowledgeWriter() + @pytest.fixture def note_writer(): """Create writer instance.""" @@ -190,7 +190,7 @@ async def sync_service( knowledge_sync_service: KnowledgeSyncService, file_change_scanner: FileChangeScanner, knowledge_parser: KnowledgeParser, - search_service: SearchService + search_service: SearchService, ) -> SyncService: """Create sync service for testing.""" return SyncService( @@ -206,12 +206,12 @@ async def search_repository(session_maker): """Create SearchRepository instance""" return SearchRepository(session_maker) + @pytest_asyncio.fixture(autouse=True) async def init_search_index(search_service): await search_service.init_search_index() - @pytest_asyncio.fixture async def search_service( search_repository: SearchRepository, @@ -228,9 +228,10 @@ async def sample_entity(entity_repository: EntityRepository) -> Entity: """Create a sample entity for testing.""" entity_data = { "name": "Test Entity", - "entity_type": EntityType.KNOWLEDGE, - "description": "A test entity", + "entity_type": "test", + "summary": "A test entity", "path_id": "test/test_entity", "file_path": "test/test_entity.md", + "content_type": "text/markdown", } return await entity_repository.create(entity_data) diff --git a/tests/markdown/test_entity_parsing.py b/tests/markdown/test_entity_parsing.py index b9b451a8..3c4d706b 100644 --- a/tests/markdown/test_entity_parsing.py +++ b/tests/markdown/test_entity_parsing.py @@ -68,7 +68,7 @@ async def test_parse_complete_file(tmp_path, valid_entity_content): # Check content assert entity.content.title == "Auth Service" assert ( - entity.content.description + entity.content.summary == "Core authentication service that handles user authentication." ) diff --git a/tests/markdown/test_knowledge_writer.py b/tests/markdown/test_knowledge_writer.py index e8c87d26..72633422 100644 --- a/tests/markdown/test_knowledge_writer.py +++ b/tests/markdown/test_knowledge_writer.py @@ -3,9 +3,9 @@ from datetime import datetime, UTC import pytest -from basic_memory.models import Entity, Observation, Relation + from basic_memory.markdown.knowledge_writer import KnowledgeWriter -from basic_memory.models.knowledge import EntityType +from basic_memory.models import Entity, Observation, Relation @pytest.fixture @@ -19,12 +19,12 @@ def sample_entity() -> Entity: return Entity( id=1, name="test_entity", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", path_id="knowledge/test_entity", file_path="knowledge/test_entity.md", - description="Test description", + summary="Test description", created_at=datetime(2025, 1, 1, tzinfo=UTC), - updated_at=datetime(2025, 1, 2, tzinfo=UTC) + updated_at=datetime(2025, 1, 2, tzinfo=UTC), ) @@ -33,7 +33,9 @@ def entity_with_observations(sample_entity: Entity) -> Entity: """Create an entity with observations.""" sample_entity.observations = [ Observation(entity_id=1, category="tech", content="First observation"), - Observation(entity_id=1, category="design", content="Second observation", context="Some context") + Observation( + entity_id=1, category="design", content="Second observation", context="Some context" + ), ] return sample_entity @@ -42,18 +44,10 @@ def entity_with_observations(sample_entity: Entity) -> Entity: def entity_with_relations(sample_entity: Entity) -> Entity: """Create an entity with relations.""" target = Entity( - id=2, - name="target_entity", - entity_type=EntityType.KNOWLEDGE, - path_id="knowledge/target_entity" + id=2, name="target_entity", entity_type="test", path_id="knowledge/target_entity" ) sample_entity.outgoing_relations = [ - Relation( - from_id=1, - to_id=2, - relation_type="connects_to", - to_entity=target - ) + Relation(from_id=1, to_id=2, relation_type="connects_to", to_entity=target) ] return sample_entity @@ -62,23 +56,22 @@ def entity_with_relations(sample_entity: Entity) -> Entity: async def test_format_frontmatter_basic(knowledge_writer: KnowledgeWriter, sample_entity: Entity): """Test basic frontmatter formatting.""" frontmatter = await knowledge_writer.format_frontmatter(sample_entity) - + assert frontmatter["id"] == "knowledge/test_entity" - assert frontmatter["type"] == EntityType.KNOWLEDGE + assert frontmatter["type"] == "test" assert frontmatter["created"] == "2025-01-01T00:00:00+00:00" assert frontmatter["modified"] == "2025-01-02T00:00:00+00:00" @pytest.mark.asyncio -async def test_format_frontmatter_with_metadata(knowledge_writer: KnowledgeWriter, sample_entity: Entity): +async def test_format_frontmatter_with_metadata( + knowledge_writer: KnowledgeWriter, sample_entity: Entity +): """Test frontmatter includes entity metadata.""" - sample_entity.entity_metadata = { - "status": "active", - "priority": "high" - } - + sample_entity.entity_metadata = {"status": "active", "priority": "high"} + frontmatter = await knowledge_writer.format_frontmatter(sample_entity) - + assert frontmatter["status"] == "active" assert frontmatter["priority"] == "high" assert frontmatter["id"] == "knowledge/test_entity" @@ -89,20 +82,19 @@ async def test_format_content_basic(knowledge_writer: KnowledgeWriter, sample_en """Test basic content formatting.""" content = "" result = await knowledge_writer.format_content(sample_entity, content) - + assert "# test_entity" in result assert "Test description" in result @pytest.mark.asyncio async def test_format_content_with_observations( - knowledge_writer: KnowledgeWriter, - entity_with_observations: Entity + knowledge_writer: KnowledgeWriter, entity_with_observations: Entity ): """Test content formatting with observations.""" content = "" result = await knowledge_writer.format_content(entity_with_observations, content) - + assert "## Observations" in result assert "- [tech] First observation" in result assert "- [design] Second observation (Some context)" in result @@ -110,13 +102,12 @@ async def test_format_content_with_observations( @pytest.mark.asyncio async def test_format_content_with_relations( - knowledge_writer: KnowledgeWriter, - entity_with_relations: Entity + knowledge_writer: KnowledgeWriter, entity_with_relations: Entity ): """Test content formatting with relations.""" content = "" result = await knowledge_writer.format_content(entity_with_relations, content) - + assert "## Relations" in result assert "- connects_to [[target_entity]]" in result @@ -125,18 +116,18 @@ async def test_format_content_with_relations( async def test_format_content_full_entity( knowledge_writer: KnowledgeWriter, entity_with_relations: Entity, - entity_with_observations: Entity + entity_with_observations: Entity, ): """Test content formatting with all entity features.""" # Combine observations and relations entity_with_relations.observations = entity_with_observations.observations content = "" result = await knowledge_writer.format_content(entity_with_relations, content) - + # Verify all sections present assert "# test_entity" in result assert "Test description" in result assert "## Observations" in result assert "- [tech] First observation" in result assert "## Relations" in result - assert "- connects_to [[target_entity]]" in result \ No newline at end of file + assert "- connects_to [[target_entity]]" in result diff --git a/tests/markdown/test_note_writer.py b/tests/markdown/test_note_writer.py index fd0b788f..9e5b2c71 100644 --- a/tests/markdown/test_note_writer.py +++ b/tests/markdown/test_note_writer.py @@ -3,9 +3,9 @@ from datetime import datetime, UTC import pytest -from basic_memory.models import Entity + from basic_memory.markdown.note_writer import NoteWriter -from basic_memory.models.knowledge import EntityType +from basic_memory.models import Entity @pytest.fixture @@ -19,11 +19,11 @@ def sample_note() -> Entity: return Entity( id=1, name="test_note", - entity_type=EntityType.NOTE, + entity_type="note", path_id="notes/test_note", file_path="notes/test_note.md", created_at=datetime(2025, 1, 1, tzinfo=UTC), - updated_at=datetime(2025, 1, 2, tzinfo=UTC) + updated_at=datetime(2025, 1, 2, tzinfo=UTC), ) @@ -31,9 +31,9 @@ def sample_note() -> Entity: async def test_format_frontmatter_basic(note_writer: NoteWriter, sample_note: Entity): """Test basic frontmatter formatting.""" frontmatter = await note_writer.format_frontmatter(sample_note) - + assert frontmatter["id"] == "notes/test_note" - assert frontmatter["type"] == EntityType.NOTE + assert frontmatter["type"] == "note" assert frontmatter["created"] == "2025-01-01T00:00:00+00:00" assert frontmatter["modified"] == "2025-01-02T00:00:00+00:00" @@ -41,13 +41,10 @@ async def test_format_frontmatter_basic(note_writer: NoteWriter, sample_note: En @pytest.mark.asyncio async def test_format_frontmatter_with_metadata(note_writer: NoteWriter, sample_note: Entity): """Test frontmatter includes entity metadata.""" - sample_note.entity_metadata = { - "category": "research", - "tags": ["python", "testing"] - } - + sample_note.entity_metadata = {"category": "research", "tags": ["python", "testing"]} + frontmatter = await note_writer.format_frontmatter(sample_note) - + assert frontmatter["category"] == "research" assert frontmatter["tags"] == ["python", "testing"] assert frontmatter["id"] == "notes/test_note" @@ -58,7 +55,7 @@ async def test_format_content_basic(note_writer: NoteWriter, sample_note: Entity """Test basic content formatting.""" content = "# Test Note\n\nThis is a test note." result = await note_writer.format_content(sample_note, content) - + assert result == content @@ -67,7 +64,7 @@ async def test_format_content_strips_whitespace(note_writer: NoteWriter, sample_ """Test content formatting strips extra whitespace.""" content = "\n\n# Test Note\n\nThis is a test note.\n\n" result = await note_writer.format_content(sample_note, content) - + assert result == "# Test Note\n\nThis is a test note." @@ -86,6 +83,6 @@ This note has: def test(): pass ```""" - + result = await note_writer.format_content(sample_note, content) - assert result == content \ No newline at end of file + assert result == content diff --git a/tests/mcp/conftest.py b/tests/mcp/conftest.py index 377e6b6e..73b5a7c6 100644 --- a/tests/mcp/conftest.py +++ b/tests/mcp/conftest.py @@ -33,7 +33,7 @@ def test_entity_data(): { "name": "Test Entity", "entity_type": "test", - "description": "", # Empty string instead of None + "summary": "", # Empty string instead of None "observations": ["This is a test observation"], } ] @@ -48,7 +48,7 @@ def test_directory_entity_data(): { "name": "Directory Organization", "entity_type": "memory", - "description": "Implemented filesystem organization by entity type", + "summary": "Implemented filesystem organization by entity type", "observations": [ "Files are now organized by type using directories like entities/project/basic_memory", "Entity IDs match filesystem paths for better mental model", diff --git a/tests/mcp/test_tool_add_observations.py b/tests/mcp/test_tool_add_observations.py index 5b9a2c08..d4b8a831 100644 --- a/tests/mcp/test_tool_add_observations.py +++ b/tests/mcp/test_tool_add_observations.py @@ -3,26 +3,25 @@ import pytest from basic_memory.mcp.tools.knowledge import create_entities, add_observations -from basic_memory.schemas.base import ObservationCategory, Entity, EntityType -from basic_memory.schemas.request import CreateEntityRequest, AddObservationsRequest, ObservationCreate +from basic_memory.schemas.base import ObservationCategory, Entity +from basic_memory.schemas.request import ( + CreateEntityRequest, + AddObservationsRequest, + ObservationCreate, +) @pytest.mark.asyncio async def test_add_basic_observation(client): """Test adding a single observation with default category.""" # First create an entity to add observations to - entity_request = CreateEntityRequest( - entities=[Entity(name="TestEntity", entity_type=EntityType.KNOWLEDGE)] - ) + entity_request = CreateEntityRequest(entities=[Entity(name="TestEntity", entity_type="test")]) result = await create_entities(entity_request) entity_id = result.entities[0].path_id # Add an observation request = AddObservationsRequest( - path_id=entity_id, - observations=[ - ObservationCreate(content="Test observation") - ] + path_id=entity_id, observations=[ObservationCreate(content="Test observation")] ) updated = await add_observations(request) @@ -37,9 +36,7 @@ async def test_add_basic_observation(client): async def test_add_categorized_observations(client): """Test adding observations with different categories.""" # Create test entity - entity_request = CreateEntityRequest( - entities=[Entity(name="TestEntity", entity_type=EntityType.KNOWLEDGE)] - ) + entity_request = CreateEntityRequest(entities=[Entity(name="TestEntity", entity_type="test")]) result = await create_entities(entity_request) entity_id = result.entities[0].path_id @@ -48,23 +45,20 @@ async def test_add_categorized_observations(client): path_id=entity_id, observations=[ ObservationCreate( - content="Implementation uses SQLite", - category=ObservationCategory.TECH + content="Implementation uses SQLite", category=ObservationCategory.TECH ), ObservationCreate( - content="Chose SQLite for simplicity", - category=ObservationCategory.DESIGN + content="Chose SQLite for simplicity", category=ObservationCategory.DESIGN ), ObservationCreate( - content="Supports atomic operations", - category=ObservationCategory.FEATURE - ) - ] + content="Supports atomic operations", category=ObservationCategory.FEATURE + ), + ], ) updated = await add_observations(request) assert len(updated.observations) == 3 - + # Find and verify each observation by category tech_obs = next(o for o in updated.observations if o.category == ObservationCategory.TECH) design_obs = next(o for o in updated.observations if o.category == ObservationCategory.DESIGN) @@ -79,9 +73,7 @@ async def test_add_categorized_observations(client): async def test_add_observations_with_context(client): """Test adding observations with shared context.""" # Create test entity - entity_request = CreateEntityRequest( - entities=[Entity(name="TestEntity", entity_type=EntityType.KNOWLEDGE)] - ) + entity_request = CreateEntityRequest(entities=[Entity(name="TestEntity", entity_type="test")]) result = await create_entities(entity_request) entity_id = result.entities[0].path_id @@ -92,21 +84,17 @@ async def test_add_observations_with_context(client): context=shared_context, observations=[ ObservationCreate( - content="Decided on file format", - category=ObservationCategory.DESIGN + content="Decided on file format", category=ObservationCategory.DESIGN ), - ObservationCreate( - content="Will use markdown", - category=ObservationCategory.TECH - ) - ] + ObservationCreate(content="Will use markdown", category=ObservationCategory.TECH), + ], ) updated = await add_observations(request) assert len(updated.observations) == 2 for obs in updated.observations: # Note: context handling depends on our schema - might need adjustment - if hasattr(obs, 'context'): + if hasattr(obs, "context"): assert obs.context == shared_context @@ -116,11 +104,7 @@ async def test_add_observations_preserves_existing(client): # Create entity with initial observation entity_request = CreateEntityRequest( entities=[ - Entity( - name="TestEntity", - entity_type=EntityType.KNOWLEDGE, - observations=["Initial observation"] - ) + Entity(name="TestEntity", entity_type="test", observations=["Initial observation"]) ] ) result = await create_entities(entity_request) @@ -130,11 +114,8 @@ async def test_add_observations_preserves_existing(client): request = AddObservationsRequest( path_id=entity_id, observations=[ - ObservationCreate( - content="New observation", - category=ObservationCategory.TECH - ) - ] + ObservationCreate(content="New observation", category=ObservationCategory.TECH) + ], ) updated = await add_observations(request) @@ -149,28 +130,19 @@ async def test_add_observations_preserves_existing(client): async def test_add_multiple_observations_same_category(client): """Test adding multiple observations in the same category.""" # Create test entity - entity_request = CreateEntityRequest( - entities=[Entity(name="TestEntity", entity_type=EntityType.KNOWLEDGE)] - ) + entity_request = CreateEntityRequest(entities=[Entity(name="TestEntity", entity_type="test")]) result = await create_entities(entity_request) entity_id = result.entities[0].path_id # Add multiple tech observations - tech_observations = [ - "Uses async/await", - "Implements SQLite backend", - "Handles UTF-8 encoding" - ] - + tech_observations = ["Uses async/await", "Implements SQLite backend", "Handles UTF-8 encoding"] + request = AddObservationsRequest( path_id=entity_id, observations=[ - ObservationCreate( - content=obs, - category=ObservationCategory.TECH - ) + ObservationCreate(content=obs, category=ObservationCategory.TECH) for obs in tech_observations - ] + ], ) updated = await add_observations(request) @@ -188,13 +160,10 @@ async def test_add_observation_to_nonexistent_entity(client): request = AddObservationsRequest( path_id="test/nonexistent", observations=[ - ObservationCreate( - content="This should fail", - category=ObservationCategory.NOTE - ) - ] + ObservationCreate(content="This should fail", category=ObservationCategory.NOTE) + ], ) # Should fail because entity doesn't exist with pytest.raises(Exception): # Adjust exception type based on your error handling - await add_observations(request) \ No newline at end of file + await add_observations(request) diff --git a/tests/mcp/test_tool_create_entities.py b/tests/mcp/test_tool_create_entities.py index 295cbfac..c9cddf5a 100644 --- a/tests/mcp/test_tool_create_entities.py +++ b/tests/mcp/test_tool_create_entities.py @@ -3,7 +3,7 @@ import pytest from basic_memory.mcp.tools.knowledge import create_entities -from basic_memory.schemas.base import ObservationCategory, Entity, EntityType +from basic_memory.schemas.base import ObservationCategory, Entity from basic_memory.schemas.request import CreateEntityRequest @@ -14,9 +14,9 @@ async def test_create_basic_entity(client): entities=[ Entity( name="TestEntity", - entity_type=EntityType.KNOWLEDGE, - description="A test entity", - observations=["First observation"] + entity_type="test", + summary="A test entity", + observations=["First observation"], ) ] ) @@ -25,13 +25,13 @@ async def test_create_basic_entity(client): # Result should be an EntityListResponse assert len(result.entities) == 1 - + # Check the created entity entity = result.entities[0] assert entity.name == "TestEntity" - assert entity.entity_type == EntityType.KNOWLEDGE + assert entity.entity_type == "test" assert entity.path_id == "test_entity" - assert entity.description == "A test entity" + assert entity.summary == "A test entity" # Check observations assert len(entity.observations) == 1 @@ -50,13 +50,9 @@ async def test_create_entity_with_multiple_observations(client): entities=[ Entity( name="TestEntity", - entity_type=EntityType.KNOWLEDGE, - description="A test entity", - observations=[ - "First observation", - "Second observation", - "Third observation" - ] + entity_type="test", + summary="A test entity", + observations=["First observation", "Second observation", "Third observation"], ) ] ) @@ -65,16 +61,12 @@ async def test_create_entity_with_multiple_observations(client): entity = result.entities[0] assert len(entity.observations) == 3 - + # Each observation should have: # - content (the observation text) # - category (default NOTE) for obs in entity.observations: - assert obs.content in [ - "First observation", - "Second observation", - "Third observation" - ] + assert obs.content in ["First observation", "Second observation", "Third observation"] assert obs.category == ObservationCategory.NOTE @@ -83,26 +75,18 @@ async def test_create_multiple_entities(client): """Test creating multiple entities in one request.""" request = CreateEntityRequest( entities=[ - Entity( - name="Entity1", - entity_type=EntityType.KNOWLEDGE, - observations=["Observation 1"] - ), - Entity( - name="Entity2", - entity_type=EntityType.KNOWLEDGE, - observations=["Observation 2"] - ) + Entity(name="Entity1", entity_type="test", observations=["Observation 1"]), + Entity(name="Entity2", entity_type="test", observations=["Observation 2"]), ] ) result = await create_entities(request) assert len(result.entities) == 2 - + # Entities should be in order assert result.entities[0].name == "Entity1" assert result.entities[1].name == "Entity2" - + # Each should have its observation assert result.entities[0].observations[0].content == "Observation 1" assert result.entities[1].observations[0].content == "Observation 2" @@ -115,8 +99,8 @@ async def test_create_entity_without_observations(client): entities=[ Entity( name="TestEntity", - entity_type=EntityType.KNOWLEDGE, - description="A test entity without observations" + entity_type="test", + summary="A test entity without observations", ) ] ) @@ -131,21 +115,14 @@ async def test_create_entity_without_observations(client): @pytest.mark.asyncio async def test_create_minimal_entity(client): """Test creating an entity with just name and type.""" - request = CreateEntityRequest( - entities=[ - Entity( - name="MinimalEntity", - entity_type=EntityType.KNOWLEDGE - ) - ] - ) + request = CreateEntityRequest(entities=[Entity(name="MinimalEntity", entity_type="test")]) result = await create_entities(request) entity = result.entities[0] assert entity.name == "MinimalEntity" - assert entity.entity_type == EntityType.KNOWLEDGE + assert entity.entity_type == "test" assert entity.path_id == "minimal_entity" - assert entity.description is None + assert entity.summary is None assert len(entity.observations) == 0 - assert len(entity.relations) == 0 \ No newline at end of file + assert len(entity.relations) == 0 diff --git a/tests/mcp/test_tool_create_relations.py b/tests/mcp/test_tool_create_relations.py index 0eaf2899..716e9575 100644 --- a/tests/mcp/test_tool_create_relations.py +++ b/tests/mcp/test_tool_create_relations.py @@ -3,9 +3,8 @@ import pytest from basic_memory.mcp.tools.knowledge import create_entities, create_relations -from basic_memory.schemas.base import Relation, Entity, EntityType +from basic_memory.schemas.base import Relation, Entity from basic_memory.schemas.request import CreateEntityRequest, CreateRelationsRequest -from basic_memory.services.exceptions import EntityNotFoundError @pytest.mark.asyncio @@ -14,8 +13,8 @@ async def test_create_basic_relation(client): # First create test entities entity_request = CreateEntityRequest( entities=[ - Entity(name="SourceEntity", entity_type=EntityType.KNOWLEDGE), - Entity(name="TargetEntity", entity_type=EntityType.KNOWLEDGE) + Entity(name="SourceEntity", entity_type="test"), + Entity(name="TargetEntity", entity_type="test"), ] ) await create_entities(entity_request) @@ -23,21 +22,17 @@ async def test_create_basic_relation(client): # Create relation between them relation_request = CreateRelationsRequest( relations=[ - Relation( - from_id="source_entity", - to_id="target_entity", - relation_type="depends_on" - ) + Relation(from_id="source_entity", to_id="target_entity", relation_type="depends_on") ] ) result = await create_relations(relation_request) assert len(result.entities) == 2 - + # Find source and target entities source = next(e for e in result.entities if e.path_id == "source_entity") target = next(e for e in result.entities if e.path_id == "target_entity") - + # Both entities should have the relation for bi-directional navigation assert len(source.relations) == 1 assert len(target.relations) == 1 @@ -61,8 +56,8 @@ async def test_create_relation_with_context(client): # Create test entities entity_request = CreateEntityRequest( entities=[ - Entity(name="Source", entity_type=EntityType.KNOWLEDGE), - Entity(name="Target", entity_type=EntityType.KNOWLEDGE) + Entity(name="Source", entity_type="test"), + Entity(name="Target", entity_type="test"), ] ) await create_entities(entity_request) @@ -73,7 +68,7 @@ async def test_create_relation_with_context(client): from_id="source", to_id="target", relation_type="implements", - context="Implementation details" + context="Implementation details", ) ] ) @@ -95,37 +90,29 @@ async def test_create_multiple_relations(client): # Create test entities entity_request = CreateEntityRequest( entities=[ - Entity(name="Entity1", entity_type=EntityType.KNOWLEDGE), - Entity(name="Entity2", entity_type=EntityType.KNOWLEDGE), - Entity(name="Entity3", entity_type=EntityType.KNOWLEDGE) + Entity(name="Entity1", entity_type="test"), + Entity(name="Entity2", entity_type="test"), + Entity(name="Entity3", entity_type="test"), ] ) await create_entities(entity_request) relation_request = CreateRelationsRequest( relations=[ - Relation( - from_id="entity1", - to_id="entity2", - relation_type="connects_to" - ), - Relation( - from_id="entity2", - to_id="entity3", - relation_type="depends_on" - ) + Relation(from_id="entity1", to_id="entity2", relation_type="connects_to"), + Relation(from_id="entity2", to_id="entity3", relation_type="depends_on"), ] ) result = await create_relations(relation_request) - + # Should return all involved entities assert len(result.entities) == 3 - + # Get entities entity1 = next(e for e in result.entities if e.path_id == "entity1") entity2 = next(e for e in result.entities if e.path_id == "entity2") entity3 = next(e for e in result.entities if e.path_id == "entity3") - + # Entity1 and Entity2 should share the connects_to relation assert len(entity1.relations) == 1 assert len(entity2.relations) == 2 # Has both relations @@ -144,8 +131,8 @@ async def test_create_bidirectional_relations(client): # Create test entities entity_request = CreateEntityRequest( entities=[ - Entity(name="Service", entity_type=EntityType.KNOWLEDGE), - Entity(name="Database", entity_type=EntityType.KNOWLEDGE) + Entity(name="Service", entity_type="test"), + Entity(name="Database", entity_type="test"), ] ) await create_entities(entity_request) @@ -153,16 +140,8 @@ async def test_create_bidirectional_relations(client): # Create relations in both directions relation_request = CreateRelationsRequest( relations=[ - Relation( - from_id="service", - to_id="database", - relation_type="depends_on" - ), - Relation( - from_id="database", - to_id="service", - relation_type="supports" - ) + Relation(from_id="service", to_id="database", relation_type="depends_on"), + Relation(from_id="database", to_id="service", relation_type="supports"), ] ) result = await create_relations(relation_request) @@ -187,20 +166,12 @@ async def test_create_bidirectional_relations(client): async def test_create_relation_with_invalid_entity(client): """Test creating a relation with non-existent entity fails.""" # Create only one of the needed entities - entity_request = CreateEntityRequest( - entities=[ - Entity(name="RealEntity", entity_type=EntityType.KNOWLEDGE) - ] - ) + entity_request = CreateEntityRequest(entities=[Entity(name="RealEntity", entity_type="test")]) await create_entities(entity_request) relation_request = CreateRelationsRequest( relations=[ - Relation( - from_id="real_entity", - to_id="non_existent_entity", - relation_type="depends_on" - ) + Relation(from_id="real_entity", to_id="non_existent_entity", relation_type="depends_on") ] ) @@ -215,20 +186,16 @@ async def test_create_duplicate_relation(client): # Create test entities entity_request = CreateEntityRequest( entities=[ - Entity(name="Source", entity_type=EntityType.KNOWLEDGE), - Entity(name="Target", entity_type=EntityType.KNOWLEDGE) + Entity(name="Source", entity_type="test"), + Entity(name="Target", entity_type="test"), ] ) await create_entities(entity_request) # Create relation - relation = Relation( - from_id="source", - to_id="target", - relation_type="connects_to" - ) + relation = Relation(from_id="source", to_id="target", relation_type="connects_to") relation_request = CreateRelationsRequest(relations=[relation]) - + # Create first relation first_result = await create_relations(relation_request) assert len(first_result.entities) == 2 @@ -237,4 +204,4 @@ async def test_create_duplicate_relation(client): # Attempt to create same relation again second_result = await create_relations(relation_request) # Current behavior: No entities returned when duplicate relation fails - assert len(second_result.entities) == 0 \ No newline at end of file + assert len(second_result.entities) == 0 diff --git a/tests/mcp/test_tool_discovery.py b/tests/mcp/test_tool_discovery.py index 5b2789fe..87b08734 100644 --- a/tests/mcp/test_tool_discovery.py +++ b/tests/mcp/test_tool_discovery.py @@ -5,8 +5,8 @@ import pytest from basic_memory.mcp.tools.discovery import ( get_observation_categories, ) -from basic_memory.schemas import Entity, CreateEntityRequest, ObservationCategoryList, EntityType from basic_memory.mcp.tools.knowledge import create_entities, add_observations +from basic_memory.schemas import Entity, CreateEntityRequest, ObservationCategoryList from basic_memory.schemas.request import ObservationCreate, AddObservationsRequest @@ -18,8 +18,8 @@ async def test_get_observation_categories(client): entities=[ Entity( name="Test Entity", - entity_type=EntityType.KNOWLEDGE, - description="Test entity", + entity_type="test", + summary="Test entity", observations=[], ) ] diff --git a/tests/mcp/test_tool_get_entity.py b/tests/mcp/test_tool_get_entity.py index 70f07cd3..73fcde61 100644 --- a/tests/mcp/test_tool_get_entity.py +++ b/tests/mcp/test_tool_get_entity.py @@ -3,7 +3,7 @@ import pytest from basic_memory.mcp.tools.knowledge import get_entity, create_entities -from basic_memory.schemas.base import Entity, ObservationCategory, EntityType +from basic_memory.schemas.base import Entity, ObservationCategory from basic_memory.schemas.request import CreateEntityRequest from basic_memory.services.exceptions import EntityNotFoundError @@ -16,9 +16,9 @@ async def test_get_basic_entity(client): entities=[ Entity( name="TestEntity", - entity_type=EntityType.KNOWLEDGE, - description="A test entity", - observations=["First observation"] + entity_type="test", + summary="A test entity", + observations=["First observation"], ) ] ) @@ -30,10 +30,10 @@ async def test_get_basic_entity(client): # Verify entity details assert entity.name == "TestEntity" - assert entity.entity_type == EntityType.KNOWLEDGE + assert entity.entity_type == "test" assert entity.path_id == "test_entity" - assert entity.description == "A test entity" - + assert entity.summary == "A test entity" + # Check observations assert len(entity.observations) == 1 obs = entity.observations[0] @@ -47,8 +47,8 @@ async def test_get_entity_with_relations(client): # Create two entities that will have a relation entity_request = CreateEntityRequest( entities=[ - Entity(name="SourceEntity", entity_type=EntityType.KNOWLEDGE), - Entity(name="TargetEntity", entity_type=EntityType.KNOWLEDGE) + Entity(name="SourceEntity", entity_type="test"), + Entity(name="TargetEntity", entity_type="test"), ] ) await create_entities(entity_request) @@ -57,14 +57,10 @@ async def test_get_entity_with_relations(client): from basic_memory.mcp.tools.knowledge import create_relations from basic_memory.schemas.request import CreateRelationsRequest from basic_memory.schemas.base import Relation - + relation_request = CreateRelationsRequest( relations=[ - Relation( - from_id="source_entity", - to_id="target_entity", - relation_type="depends_on" - ) + Relation(from_id="source_entity", to_id="target_entity", relation_type="depends_on") ] ) await create_relations(relation_request) @@ -83,11 +79,7 @@ async def test_get_entity_with_categorized_observations(client): # Create entity with categorized observations entity_request = CreateEntityRequest( entities=[ - Entity( - name="TestEntity", - entity_type=EntityType.KNOWLEDGE, - description="Test entity with categories" - ) + Entity(name="TestEntity", entity_type="test", summary="Test entity with categories") ] ) result = await create_entities(entity_request) @@ -100,19 +92,10 @@ async def test_get_entity_with_categorized_observations(client): obs_request = AddObservationsRequest( path_id=path_id, observations=[ - ObservationCreate( - content="Technical detail", - category=ObservationCategory.TECH - ), - ObservationCreate( - content="Design decision", - category=ObservationCategory.DESIGN - ), - ObservationCreate( - content="Feature note", - category=ObservationCategory.FEATURE - ) - ] + ObservationCreate(content="Technical detail", category=ObservationCategory.TECH), + ObservationCreate(content="Design decision", category=ObservationCategory.DESIGN), + ObservationCreate(content="Feature note", category=ObservationCategory.FEATURE), + ], ) await add_observations(obs_request) diff --git a/tests/mcp/test_tool_open_nodes.py b/tests/mcp/test_tool_open_nodes.py index 33fb2116..c3880d9b 100644 --- a/tests/mcp/test_tool_open_nodes.py +++ b/tests/mcp/test_tool_open_nodes.py @@ -2,10 +2,10 @@ import pytest -from basic_memory.mcp.tools.search import open_nodes from basic_memory.mcp.tools.knowledge import create_entities +from basic_memory.mcp.tools.search import open_nodes from basic_memory.schemas import EntityListResponse -from basic_memory.schemas.base import Entity, EntityType +from basic_memory.schemas.base import Entity from basic_memory.schemas.request import CreateEntityRequest, OpenNodesRequest @@ -15,16 +15,8 @@ async def test_open_multiple_entities(client): # Create some test entities entity_request = CreateEntityRequest( entities=[ - Entity( - name="Entity1", - entity_type=EntityType.KNOWLEDGE, - description="First test entity" - ), - Entity( - name="Entity2", - entity_type=EntityType.KNOWLEDGE, - description="Second test entity" - ) + Entity(name="Entity1", entity_type="test", summary="First test entity"), + Entity(name="Entity2", entity_type="test", summary="Second test entity"), ] ) create_result = await create_entities(entity_request) @@ -35,14 +27,14 @@ async def test_open_multiple_entities(client): result = await open_nodes(request) assert isinstance(result, EntityListResponse) response = EntityListResponse.model_validate(result) - + # Verify we got a dictionary with both entities assert len(response.entities) == 2 - + for response_entity in response.entities: assert response_entity.path_id in path_ids assert response_entity.name in ["Entity1", "Entity2"] - + @pytest.mark.asyncio async def test_open_nodes_with_details(client): @@ -52,9 +44,9 @@ async def test_open_nodes_with_details(client): entities=[ Entity( name="DetailedEntity", - entity_type=EntityType.KNOWLEDGE, - description="Test entity with details", - observations=["First observation", "Second observation"] + entity_type="test", + summary="Test entity with details", + observations=["First observation", "Second observation"], ) ] ) @@ -69,8 +61,8 @@ async def test_open_nodes_with_details(client): # Verify all details are present entity = response.entities[0] assert entity.name == "DetailedEntity" - assert entity.entity_type == EntityType.KNOWLEDGE - assert entity.description == "Test entity with details" + assert entity.entity_type == "test" + assert entity.summary == "Test entity with details" assert len(entity.observations) == 2 @@ -80,16 +72,8 @@ async def test_open_nodes_with_relations(client): # Create related entities entity_request = CreateEntityRequest( entities=[ - Entity( - name="Service", - entity_type=EntityType.KNOWLEDGE, - description="A service" - ), - Entity( - name="Database", - entity_type=EntityType.KNOWLEDGE, - description="A database" - ) + Entity(name="Service", entity_type="test", summary="A service"), + Entity(name="Database", entity_type="test", summary="A database"), ] ) create_result = await create_entities(entity_request) @@ -101,13 +85,7 @@ async def test_open_nodes_with_relations(client): from basic_memory.schemas.base import Relation relation_request = CreateRelationsRequest( - relations=[ - Relation( - from_id=path_ids[0], - to_id=path_ids[1], - relation_type="depends_on" - ) - ] + relations=[Relation(from_id=path_ids[0], to_id=path_ids[1], relation_type="depends_on")] ) await create_relations(relation_request) @@ -125,21 +103,12 @@ async def test_open_nodes_with_relations(client): async def test_open_nonexistent_nodes(client): """Test behavior when some requested nodes don't exist.""" # First create one real entity - entity_request = CreateEntityRequest( - entities=[ - Entity( - name="RealEntity", - entity_type=EntityType.KNOWLEDGE - ) - ] - ) + entity_request = CreateEntityRequest(entities=[Entity(name="RealEntity", entity_type="test")]) create_result = await create_entities(entity_request) real_path_id = create_result.entities[0].path_id # Try to open both real and non-existent - request = OpenNodesRequest( - path_ids=[real_path_id, "nonexistent"] - ) + request = OpenNodesRequest(path_ids=[real_path_id, "nonexistent"]) result = await open_nodes(request) response = EntityListResponse.model_validate(result) @@ -152,14 +121,7 @@ async def test_open_nonexistent_nodes(client): async def test_open_single_node(client): """Test behavior with single path_id.""" # Create an entity - entity_request = CreateEntityRequest( - entities=[ - Entity( - name="SingleEntity", - entity_type=EntityType.KNOWLEDGE - ) - ] - ) + entity_request = CreateEntityRequest(entities=[Entity(name="SingleEntity", entity_type="test")]) create_result = await create_entities(entity_request) path_id = create_result.entities[0].path_id @@ -170,4 +132,4 @@ async def test_open_single_node(client): # Should get just that entity assert len(response.entities) == 1 - assert path_id in response.entities[0].path_id \ No newline at end of file + assert path_id in response.entities[0].path_id diff --git a/tests/repository/test_entity_repository.py b/tests/repository/test_entity_repository.py index d218399f..1231c83c 100644 --- a/tests/repository/test_entity_repository.py +++ b/tests/repository/test_entity_repository.py @@ -1,15 +1,13 @@ """Tests for the EntityRepository.""" -from datetime import datetime, UTC +from datetime import datetime import pytest import pytest_asyncio -from sqlalchemy import select, text -from sqlalchemy.exc import IntegrityError +from sqlalchemy import select from basic_memory import db from basic_memory.models import Entity, Observation, Relation -from basic_memory.models.knowledge import EntityType from basic_memory.repository.entity_repository import EntityRepository @@ -31,17 +29,19 @@ async def related_entities(session_maker): async with db.scoped_session(session_maker) as session: source = Entity( name="source", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", path_id="source/source", file_path="source/source.md", - description="Source entity", + summary="Source entity", + content_type="text/markdown", ) target = Entity( name="target", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", path_id="target/target", file_path="target/target.md", - description="Target entity", + summary="Target entity", + content_type="text/markdown", ) session.add(source) session.add(target) @@ -58,10 +58,11 @@ async def test_create_entity(entity_repository: EntityRepository): """Test creating a new entity""" entity_data = { "name": "Test", - "entity_type": EntityType.KNOWLEDGE, + "entity_type": "test", "path_id": "test/test", "file_path": "test/test.md", - "description": "Test description", + "summary": "Test description", + "content_type": "text/markdown", } entity = await entity_repository.create(entity_data) @@ -91,17 +92,19 @@ async def test_create_all(entity_repository: EntityRepository): entity_data = [ { "name": "Test_1", - "entity_type": EntityType.KNOWLEDGE, + "entity_type": "test", "path_id": "test/test_1", "file_path": "test/test_1.md", - "description": "Test description", + "summary": "Test description", + "content_type": "text/markdown", }, { "name": "Test-2", - "entity_type": EntityType.KNOWLEDGE, + "entity_type": "test", "path_id": "test/test_2", "file_path": "test/test_2.md", - "description": "Test description", + "summary": "Test description", + "content_type": "text/markdown", }, ] entities = await entity_repository.create_all(entity_data) @@ -127,10 +130,11 @@ async def test_create_entity_null_description(session_maker, entity_repository: """Test creating an entity with null description""" entity_data = { "name": "Test", - "entity_type": EntityType.KNOWLEDGE, + "entity_type": "test", "path_id": "test/test", "file_path": "test/test.md", - "description": None, + "content_type": "text/markdown", + "summary": None, } entity = await entity_repository.create(entity_data) @@ -164,7 +168,7 @@ async def test_find_by_id(entity_repository: EntityRepository, sample_entity: En async def test_update_entity(entity_repository: EntityRepository, sample_entity: Entity): """Test updating an entity""" updated = await entity_repository.update( - sample_entity.id, {"description": "Updated description"} + sample_entity.id, {"summary": "Updated description"} ) assert updated is not None assert updated.summary == "Updated description" @@ -182,7 +186,7 @@ async def test_update_entity(entity_repository: EntityRepository, sample_entity: @pytest.mark.asyncio async def test_update_entity_to_null(entity_repository: EntityRepository, sample_entity: Entity): """Test updating an entity's description to null""" - updated = await entity_repository.update(sample_entity.id, {"description": None}) + updated = await entity_repository.update(sample_entity.id, {"summary": None}) assert updated is not None assert updated.summary is None @@ -269,8 +273,6 @@ async def test_delete_nonexistent_entity(entity_repository: EntityRepository): assert result is False - - @pytest_asyncio.fixture async def test_entities(session_maker): """Create multiple test entities.""" @@ -278,24 +280,27 @@ async def test_entities(session_maker): entities = [ Entity( name="entity1", - entity_type=EntityType.KNOWLEDGE, - description="First test entity", + entity_type="test", + summary="First test entity", path_id="type1/entity1", file_path="type1/entity1.md", + content_type= "text/markdown", ), Entity( name="entity2", - entity_type=EntityType.KNOWLEDGE, - description="Second test entity", + entity_type="test", + summary="Second test entity", path_id="type1/entity2", file_path="type1/entity2.md", + content_type="text/markdown", ), Entity( name="entity3", - entity_type=EntityType.KNOWLEDGE, - description="Third test entity", + entity_type="test", + summary="Third test entity", path_id="type2/entity3", file_path="type2/entity3.md", + content_type="text/markdown", ), ] session.add_all(entities) @@ -384,31 +389,34 @@ async def test_delete_by_path_ids_with_observations( @pytest.mark.asyncio async def test_list_entities_with_related(entity_repository: EntityRepository, session_maker): """Test listing entities with related entities included.""" - + # Create test entities async with db.scoped_session(session_maker) as session: # Core entities core = Entity( name="core_service", - entity_type=EntityType.NOTE, + entity_type="note", path_id="service/core", file_path="service/core.md", - description="Core service" + summary="Core service", + content_type="text/markdown", ) dbe = Entity( name="db_service", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", path_id="service/db", file_path="service/db.md", - description="Database service" + summary="Database service", + content_type="text/markdown", ) # Related entity of different type config = Entity( name="service_config", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", path_id="config/service", file_path="config/service.md", - description="Service configuration" + summary="Service configuration", + content_type="text/markdown", ) session.add_all([core, dbe, config]) await session.flush() @@ -418,23 +426,19 @@ async def test_list_entities_with_related(entity_repository: EntityRepository, s # core -> db (depends_on) Relation(from_id=core.id, to_id=dbe.id, relation_type="depends_on"), # config -> core (configures) - Relation(from_id=config.id, to_id=core.id, relation_type="configures") + Relation(from_id=config.id, to_id=core.id, relation_type="configures"), ] session.add_all(relations) # Test 1: List without related entities - services = await entity_repository.list_entities( - entity_type=EntityType.KNOWLEDGE, - include_related=False - ) + services = await entity_repository.list_entities(entity_type="test", include_related=False) assert len(services) == 2 service_names = {s.name for s in services} assert service_names == {"service_config", "db_service"} # Test 2: List services with related entities services_and_related = await entity_repository.list_entities( - entity_type=EntityType.KNOWLEDGE, - include_related=True + entity_type="test", include_related=True ) assert len(services_and_related) == 3 # Should include both services and the config @@ -444,4 +448,4 @@ async def test_list_entities_with_related(entity_repository: EntityRepository, s # Test 3: Verify relations are loaded core_service = next(e for e in services_and_related if e.name == "core_service") assert len(core_service.outgoing_relations) > 0 # Has incoming relation from config - assert len(core_service.incoming_relations) > 0 # Has outgoing relation to db + assert len(core_service.incoming_relations) > 0 # Has outgoing relation to db diff --git a/tests/repository/test_observation_repository.py b/tests/repository/test_observation_repository.py index 29be93dc..ff4c8046 100644 --- a/tests/repository/test_observation_repository.py +++ b/tests/repository/test_observation_repository.py @@ -7,7 +7,6 @@ from sqlalchemy.ext.asyncio import async_sessionmaker from basic_memory import db from basic_memory.models import Entity, Observation -from basic_memory.models.knowledge import EntityType from basic_memory.repository.observation_repository import ObservationRepository @@ -90,10 +89,11 @@ async def test_delete_observations(session_maker: async_sessionmaker, repo): async with db.scoped_session(session_maker) as session: entity = Entity( name="test_entity", - entity_type=EntityType.KNOWLEDGE, - description="Test entity", + entity_type="test", + summary="Test entity", path_id="test/test_entity", file_path="test/test_entity.md", + content_type="text/markdown", ) session.add(entity) await session.flush() @@ -119,10 +119,11 @@ async def test_delete_observation_by_id(session_maker: async_sessionmaker, repo) async with db.scoped_session(session_maker) as session: entity = Entity( name="test_entity", - entity_type=EntityType.KNOWLEDGE, - description="Test entity", + entity_type="test", + summary="Test entity", path_id="test/test_entity", file_path="test/test_entity.md", + content_type="text/markdown", ) session.add(entity) await session.flush() @@ -147,10 +148,11 @@ async def test_delete_observation_by_content(session_maker: async_sessionmaker, async with db.scoped_session(session_maker) as session: entity = Entity( name="test_entity", - entity_type=EntityType.KNOWLEDGE, - description="Test entity", + entity_type="test", + summary="Test entity", path_id="test/test_entity", file_path="test/test_entity.md", + content_type="text/markdown", ) session.add(entity) await session.flush() @@ -177,10 +179,11 @@ async def test_find_by_category(session_maker: async_sessionmaker, repo): async with db.scoped_session(session_maker) as session: entity = Entity( name="test_entity", - entity_type=EntityType.KNOWLEDGE, - description="Test entity", + entity_type="test", + summary="Test entity", path_id="test/test_entity", file_path="test/test_entity.md", + content_type="text/markdown", ) session.add(entity) await session.flush() @@ -218,10 +221,11 @@ async def test_observation_categories(session_maker: async_sessionmaker, repo): async with db.scoped_session(session_maker) as session: entity = Entity( name="test_entity", - entity_type=EntityType.KNOWLEDGE, - description="Test entity", + entity_type="test", + summary="Test entity", path_id="test/test_entity", file_path="test/test_entity.md", + content_type="text/markdown", ) session.add(entity) await session.flush() @@ -265,10 +269,11 @@ async def test_find_by_category_case_sensitivity(session_maker: async_sessionmak async with db.scoped_session(session_maker) as session: entity = Entity( name="test_entity", - entity_type=EntityType.KNOWLEDGE, - description="Test entity", + entity_type="test", + summary="Test entity", path_id="test/test_entity", file_path="test/test_entity.md", + content_type="text/markdown", ) session.add(entity) await session.flush() diff --git a/tests/repository/test_relation_repository.py b/tests/repository/test_relation_repository.py index bff0f8ed..c0625ebf 100644 --- a/tests/repository/test_relation_repository.py +++ b/tests/repository/test_relation_repository.py @@ -6,7 +6,6 @@ import sqlalchemy from basic_memory import db from basic_memory.models import Entity, Relation -from basic_memory.models.knowledge import EntityType from basic_memory.repository.relation_repository import RelationRepository @@ -15,10 +14,11 @@ async def source_entity(session_maker): """Create a source entity for testing relations.""" entity = Entity( name="test_source", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", path_id="source/test_source", file_path="source/test_source.md", - description="Source entity", + summary="Source entity", + content_type="text/markdown", ) async with db.scoped_session(session_maker) as session: session.add(entity) @@ -31,10 +31,11 @@ async def target_entity(session_maker): """Create a target entity for testing relations.""" entity = Entity( name="test_target", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", path_id="target/test_target", file_path="target/test_target.md", - description="Target entity", + summary="Target entity", + content_type="text/markdown", ) async with db.scoped_session(session_maker) as session: session.add(entity) @@ -60,10 +61,11 @@ async def related_entity(entity_repository): """Create a second entity for testing relations""" entity_data = { "name": "Related Entity", - "entity_type": EntityType.KNOWLEDGE, + "entity_type": "test", "path_id": "test/related_entity", "file_path": "test/related_entity.md", - "description": "A related test entity", + "summary": "A related test entity", + "content_type": "text/markdown", "references": "", } return await entity_repository.create(entity_data) @@ -158,12 +160,15 @@ async def test_find_by_entities( assert relations[0].id == sample_relation.id assert relations[0].relation_type == sample_relation.relation_type + @pytest.mark.asyncio async def test_find_relation(relation_repository: RelationRepository, sample_relation: Relation): """Test finding relations by type""" - relation = await relation_repository.find_relation(from_path_id=sample_relation.from_entity.path_id, - to_path_id=sample_relation.to_entity.path_id, - relation_type=sample_relation.relation_type) + relation = await relation_repository.find_relation( + from_path_id=sample_relation.from_entity.path_id, + to_path_id=sample_relation.to_entity.path_id, + relation_type=sample_relation.relation_type, + ) assert relation.id == sample_relation.id diff --git a/tests/schemas/test_schemas.py b/tests/schemas/test_schemas.py index 2b4f41ea..c3d7f8d7 100644 --- a/tests/schemas/test_schemas.py +++ b/tests/schemas/test_schemas.py @@ -20,7 +20,7 @@ def test_entity_in_minimal(): entity = Entity.model_validate(data) assert entity.name == "test_entity" assert entity.entity_type == "knowledge" - assert entity.description is None + assert entity.summary is None assert entity.observations == [] @@ -29,13 +29,13 @@ def test_entity_in_complete(): data = { "name": "test_entity", "entity_type": "knowledge", - "description": "A test entity", + "summary": "A test entity", "observations": ["Test observation"], } entity = Entity.model_validate(data) assert entity.name == "test_entity" assert entity.entity_type == "knowledge" - assert entity.description == "A test entity" + assert entity.summary == "A test entity" assert len(entity.observations) == 1 assert entity.observations[0] == "Test observation" @@ -43,7 +43,7 @@ def test_entity_in_complete(): def test_entity_in_validation(): """Test validation errors for EntityIn.""" with pytest.raises(ValidationError): - Entity.model_validate({}) # Missing required fields + Entity.model_validate({"file_path": "test"}) # Missing required fields with pytest.raises(ValidationError): Entity.model_validate({"name": "test"}) # Missing entityType @@ -85,12 +85,12 @@ def test_create_entities_input(): data = { "entities": [ {"name": "entity1", "entity_type": "knowledge"}, - {"name": "entity2", "entity_type": "knowledge", "description": "test description"}, + {"name": "entity2", "entity_type": "knowledge", "summary": "test description"}, ] } create_input = CreateEntityRequest.model_validate(data) assert len(create_input.entities) == 2 - assert create_input.entities[1].description == "test description" + assert create_input.entities[1].summary == "test description" # Empty entities list should fail with pytest.raises(ValidationError): @@ -104,7 +104,8 @@ def test_entity_out_from_attributes(): "path_id": "test/test", "name": "test", "entity_type": "knowledge", - "description": "test description", + "content_type": "text/markdown", + "summary": "test description", "observations": [{"id": 1, "content": "test obs", "context": None}], "relations": [ {"id": 1, "from_id": "test/test", "to_id": "test/test", "relation_type": "test", "context": None} @@ -112,7 +113,7 @@ def test_entity_out_from_attributes(): } entity = EntityResponse.model_validate(db_data) assert entity.path_id == "test/test" - assert entity.description == "test description" + assert entity.summary == "test description" assert len(entity.observations) == 1 assert len(entity.relations) == 1 @@ -121,7 +122,7 @@ def test_optional_fields(): """Test handling of optional fields.""" # Create with no optional fields entity = Entity.model_validate({"name": "test", "entity_type": "knowledge"}) - assert entity.description is None + assert entity.summary is None assert entity.observations == [] # Create with empty optional fields @@ -129,18 +130,18 @@ def test_optional_fields(): { "name": "test", "entity_type": "knowledge", - "description": None, + "summary": None, "observations": [], } ) - assert entity.description is None + assert entity.summary is None assert entity.observations == [] # Create with some optional fields entity = Entity.model_validate( - {"name": "test", "entity_type": "knowledge", "description": "test", "observations": []} + {"name": "test", "entity_type": "knowledge", "summary": "test", "observations": []} ) - assert entity.description == "test" + assert entity.summary == "test" assert entity.observations == [] diff --git a/tests/services/test_activity_service.py b/tests/services/test_activity_service.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/services/test_entity_service.py b/tests/services/test_entity_service.py index a46caaad..51c369fb 100644 --- a/tests/services/test_entity_service.py +++ b/tests/services/test_entity_service.py @@ -6,7 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from basic_memory.models import Entity as EntityModel from basic_memory.repository.entity_repository import EntityRepository -from basic_memory.schemas import Entity as EntitySchema, EntityType +from basic_memory.schemas import Entity as EntitySchema from basic_memory.services.entity_service import EntityService from basic_memory.services.exceptions import EntityNotFoundError @@ -29,8 +29,8 @@ async def test_create_entity(entity_service: EntityService): """Test successful entity creation.""" entity_data = EntitySchema( name="TestEntity", - entity_type=EntityType.KNOWLEDGE, - description="A test entity description", + entity_type="test", + summary="A test entity description", observations=["this is a test observation"], ) @@ -42,7 +42,7 @@ async def test_create_entity(entity_service: EntityService): assert entity.name == "TestEntity" assert entity.path_id == entity_data.path_id assert entity.file_path == entity_data.file_path - assert entity.entity_type == EntityType.KNOWLEDGE + assert entity.entity_type == "test" assert entity.summary == "A test entity description" assert entity.created_at is not None assert entity.observations[0].content == "this is a test observation" @@ -52,7 +52,7 @@ async def test_create_entity(entity_service: EntityService): retrieved = await entity_service.get_by_path_id(entity_data.path_id) assert retrieved.summary == "A test entity description" assert retrieved.name == "TestEntity" - assert retrieved.entity_type == EntityType.KNOWLEDGE + assert retrieved.entity_type == "test" assert retrieved.summary == "A test entity description" assert retrieved.created_at is not None assert retrieved.observations[0].content == "this is a test observation" @@ -63,14 +63,14 @@ async def test_create_entities(entity_service: EntityService): entity_data = [ EntitySchema( name="TestEntity1", - entity_type=EntityType.KNOWLEDGE, - description="A test entity description", + entity_type="test", + summary="A test entity description", observations=["this is a test observation"], ), EntitySchema( name="TestEntity2", - entity_type=EntityType.KNOWLEDGE, - description="A test entity description", + entity_type="test", + summary="A test entity description", observations=["this is a test observation"], ), ] @@ -83,7 +83,7 @@ async def test_create_entities(entity_service: EntityService): entity1 = entities[0] assert isinstance(entity1, EntityModel) assert entity1.name == "TestEntity1" - assert entity1.entity_type == EntityType.KNOWLEDGE + assert entity1.entity_type == "test" assert entity1.summary == "A test entity description" assert entity1.created_at is not None assert entity1.observations[0].content == "this is a test observation" @@ -92,7 +92,7 @@ async def test_create_entities(entity_service: EntityService): entity2 = entities[1] assert isinstance(entity1, EntityModel) assert entity2.name == "TestEntity2" - assert entity2.entity_type == EntityType.KNOWLEDGE + assert entity2.entity_type == "test" assert entity2.summary == "A test entity description" assert entity2.created_at is not None assert entity2.observations[0].content == "this is a test observation" @@ -109,16 +109,16 @@ async def test_get_by_path_id(entity_service: EntityService): """Test finding entity by type and name combination.""" entity1_data = EntitySchema( name="TestEntity1", - entity_type=EntityType.KNOWLEDGE, - description="First test entity", + entity_type="test", + summary="First test entity", observations=[], ) entity1 = await entity_service.create_entity(entity1_data) entity2_data = EntitySchema( - name="TestEntity2", - entity_type=EntityType.KNOWLEDGE, - description="Second test entity", + name="TestEntity2", + entity_type="test", + summary="Second test entity", observations=[], ) entity2 = await entity_service.create_entity(entity2_data) @@ -144,7 +144,7 @@ async def test_get_by_path_id(entity_service: EntityService): async def test_create_entity_no_description(entity_service: EntityService): """Test creating entity without description (should be None).""" - entity_data = EntitySchema(name="TestEntity", entity_type=EntityType.KNOWLEDGE, observations=[]) + entity_data = EntitySchema(name="TestEntity", entity_type="test", observations=[]) entity = await entity_service.create_entity(entity_data) assert entity.summary is None @@ -158,8 +158,8 @@ async def test_get_entity_success(entity_service: EntityService): """Test successful entity retrieval.""" entity_data = EntitySchema( name="TestEntity", - entity_type=EntityType.KNOWLEDGE, - description="Test description", + entity_type="test", + summary="Test description", observations=[], ) await entity_service.create_entity(entity_data) @@ -169,7 +169,7 @@ async def test_get_entity_success(entity_service: EntityService): assert isinstance(retrieved, EntityModel) assert retrieved.name == "TestEntity" - assert retrieved.entity_type == EntityType.KNOWLEDGE + assert retrieved.entity_type == "test" assert retrieved.summary == "Test description" @@ -177,15 +177,15 @@ async def test_update_entity_description(entity_service: EntityService): """Test updating an entity's description.""" entity_data = EntitySchema( name="TestEntity", - entity_type=EntityType.KNOWLEDGE, - description="Initial description", + entity_type="test", + summary="Initial description", observations=[], ) await entity_service.create_entity(entity_data) # Update description using path_id updated = await entity_service.update_entity( - entity_data.path_id, {"description": "Updated description"} + entity_data.path_id, {"summary": "Updated description"} ) assert updated.summary == "Updated description" @@ -198,14 +198,14 @@ async def test_update_entity_description_to_none(entity_service: EntityService): """Test updating an entity's description to None.""" entity_data = EntitySchema( name="TestEntity", - entity_type=EntityType.KNOWLEDGE, - description="Initial description", + entity_type="test", + summary="Initial description", observations=[], ) await entity_service.create_entity(entity_data) # Update description to None using path_id - updated = await entity_service.update_entity(entity_data.path_id, {"description": None}) + updated = await entity_service.update_entity(entity_data.path_id, {"summary": None}) assert updated.summary is None # Verify after retrieval @@ -217,7 +217,7 @@ async def test_delete_entity_success(entity_service: EntityService): """Test successful entity deletion.""" entity_data = EntitySchema( name="TestEntity", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", observations=[], ) await entity_service.create_entity(entity_data) @@ -249,8 +249,8 @@ async def test_create_entity_with_special_chars(entity_service: EntityService): description = "Description with $pecial chars & symbols!" entity_data = EntitySchema( name=name, - entity_type=EntityType.KNOWLEDGE, - description=description, + entity_type="test", + summary=description, ) entity = await entity_service.create_entity(entity_data) @@ -267,8 +267,8 @@ async def test_create_entity_long_description(entity_service: EntityService): long_description = "A" * 1000 # 1000 character description entity_data = EntitySchema( name="TestEntity", - entity_type=EntityType.KNOWLEDGE, - description=long_description, + entity_type="test", + summary=long_description, observations=[], ) @@ -285,14 +285,14 @@ async def test_open_nodes_by_path_ids(entity_service: EntityService): # Create test entities entity1_data = EntitySchema( name="Entity1", - entity_type=EntityType.KNOWLEDGE, - description="First entity", + entity_type="test", + summary="First entity", observations=[], ) entity2_data = EntitySchema( name="Entity2", - entity_type=EntityType.KNOWLEDGE, - description="Second entity", + entity_type="test", + summary="Second entity", observations=[], ) await entity_service.create_entity(entity1_data) @@ -318,8 +318,8 @@ async def test_open_nodes_some_not_found(entity_service: EntityService): # Create one test entity entity_data = EntitySchema( name="Entity1", - entity_type=EntityType.KNOWLEDGE, - description="Test entity", + entity_type="test", + summary="Test entity", observations=[], ) await entity_service.create_entity(entity_data) @@ -337,14 +337,14 @@ async def test_delete_entities_by_path_ids(entity_service: EntityService): # Create test entities entity1_data = EntitySchema( name="Entity1", - entity_type=EntityType.KNOWLEDGE, - description="First entity", + entity_type="test", + summary="First entity", observations=[], ) entity2_data = EntitySchema( name="Entity2", - entity_type=EntityType.KNOWLEDGE, - description="Second entity", + entity_type="test", + summary="Second entity", observations=[], ) await entity_service.create_entity(entity1_data) diff --git a/tests/services/test_knowledge_service.py b/tests/services/test_knowledge_service.py index 28dbb46d..15a16d00 100644 --- a/tests/services/test_knowledge_service.py +++ b/tests/services/test_knowledge_service.py @@ -6,7 +6,7 @@ import pytest import yaml from basic_memory.models import Entity as EntityModel -from basic_memory.models.knowledge import EntityType, ObservationCategory +from basic_memory.models.knowledge import ObservationCategory from basic_memory.schemas import Entity as EntitySchema, Relation as RelationSchema from basic_memory.schemas.request import ObservationCreate from basic_memory.services import EntityService @@ -20,8 +20,8 @@ async def test_get_entity_path(knowledge_service: KnowledgeService): id=1, path_id="test-entity", name="test-entity", - entity_type=EntityType.KNOWLEDGE, - description="Test entity", + entity_type="test", + summary="Test entity", ) path = knowledge_service.get_entity_path(entity) assert path == Path(knowledge_service.base_path / "test-entity.md") @@ -31,9 +31,7 @@ async def test_get_entity_path(knowledge_service: KnowledgeService): async def test_create_entity(knowledge_service: KnowledgeService): """Should create entity in DB and write file correctly.""" # Setup - entity_schema = EntitySchema( - name="test-entity", entity_type=EntityType.KNOWLEDGE, description="Test entity" - ) + entity_schema = EntitySchema(name="test-entity", entity_type="test", summary="Test entity") # Execute created = await knowledge_service.create_entity(entity_schema) @@ -41,7 +39,7 @@ async def test_create_entity(knowledge_service: KnowledgeService): # Verify DB entity assert created.name == entity_schema.name assert created.entity_type == entity_schema.entity_type - assert created.summary == entity_schema.description + assert created.summary == entity_schema.summary assert created.checksum is not None assert created.path_id == "test_entity" assert created.file_path == "test_entity.md" @@ -65,9 +63,7 @@ async def test_create_entity(knowledge_service: KnowledgeService): async def test_create_multiple_entities(knowledge_service: KnowledgeService): """Should create multiple entities successfully.""" entities = [ - EntitySchema( - name=f"entity-{i}", entity_type=EntityType.KNOWLEDGE, description=f"Test entity {i}" - ) + EntitySchema(name=f"entity-{i}", entity_type="test", summary=f"Test entity {i}") for i in range(3) ] @@ -85,10 +81,10 @@ async def test_create_relations(knowledge_service: KnowledgeService, entity_serv """Should create relations and update related entity files.""" # Create test entities entity1 = await knowledge_service.create_entity( - EntitySchema(name="entity1", entity_type=EntityType.KNOWLEDGE, description="Test entity 1") + EntitySchema(name="entity1", entity_type="test", summary="Test entity 1") ) entity2 = await knowledge_service.create_entity( - EntitySchema(name="entity2", entity_type=EntityType.KNOWLEDGE, description="Test entity 2") + EntitySchema(name="entity2", entity_type="test", summary="Test entity 2") ) # Create relation @@ -124,15 +120,15 @@ async def test_update_knowledge_entity_description(knowledge_service: KnowledgeS entity = await knowledge_service.create_entity( EntitySchema( name="test", - entity_type=EntityType.KNOWLEDGE, - description="Test entity", + entity_type="test", + summary="Test entity", entity_metadata={"status": "draft"}, ) ) # Update description updated = await knowledge_service.update_entity( - entity.path_id, description="Updated description" + entity.path_id, summary="Updated description" ) # Verify file has new description but preserved metadata @@ -154,8 +150,8 @@ async def test_update_note_entity_content(knowledge_service: KnowledgeService): entity = await knowledge_service.create_entity( EntitySchema( name="test", - entity_type=EntityType.NOTE, - description="Test note", + entity_type="note", + summary="Test note", entity_metadata={"status": "draft"}, ) ) @@ -184,8 +180,8 @@ async def test_update_entity_name(knowledge_service: KnowledgeService): entity = await knowledge_service.create_entity( EntitySchema( name="test", - entity_type=EntityType.KNOWLEDGE, - description="Test entity", + entity_type="test", + summary="Test entity", entity_metadata={"status": "draft"}, ) ) @@ -216,26 +212,20 @@ async def test_update_entity_type_note_to_knowledge(knowledge_service: Knowledge entity = await knowledge_service.create_entity( EntitySchema( name="test", - entity_type=EntityType.NOTE, - description="Test note", - entity_metadata={"status": "draft"} + entity_type="note", + summary="Test note", + entity_metadata={"status": "draft"}, ) ) # First update with some content as a note - await knowledge_service.update_entity( - entity.path_id, - content=initial_content - ) + await knowledge_service.update_entity(entity.path_id, content=initial_content) # Then update to knowledge type - updated = await knowledge_service.update_entity( - entity.path_id, - entity_type=EntityType.KNOWLEDGE - ) + updated = await knowledge_service.update_entity(entity.path_id, entity_type="test") # Verify type was updated in DB - assert updated.entity_type == EntityType.KNOWLEDGE + assert updated.entity_type == "test" # Verify frontmatter was updated file_path = knowledge_service.get_entity_path(updated) @@ -243,7 +233,7 @@ async def test_update_entity_type_note_to_knowledge(knowledge_service: Knowledge _, frontmatter, _ = content.split("---", 2) metadata = yaml.safe_load(frontmatter) - assert metadata["type"] == EntityType.KNOWLEDGE + assert metadata["type"] == "test" # Verify content format changed to knowledge style (structured) assert "# test" in content @@ -257,9 +247,9 @@ async def test_update_entity_type_knowledge_to_note(knowledge_service: Knowledge entity = await knowledge_service.create_entity( EntitySchema( name="test", - entity_type=EntityType.KNOWLEDGE, - description="Test knowledge entity", - entity_metadata={"status": "draft"} + entity_type="test", + summary="Test knowledge entity", + entity_metadata={"status": "draft"}, ) ) @@ -272,13 +262,11 @@ async def test_update_entity_type_knowledge_to_note(knowledge_service: Knowledge # Update to note type with new content new_content = "# Test Note\n\nConverted to note format." updated = await knowledge_service.update_entity( - entity.path_id, - entity_type=EntityType.NOTE, - content=new_content + entity.path_id, entity_type="note", content=new_content ) # Verify type was updated in DB - assert updated.entity_type == EntityType.NOTE + assert updated.entity_type == "note" # Verify frontmatter was updated file_path = knowledge_service.get_entity_path(updated) @@ -286,7 +274,7 @@ async def test_update_entity_type_knowledge_to_note(knowledge_service: Knowledge _, frontmatter, _ = content.split("---", 2) metadata = yaml.safe_load(frontmatter) - assert metadata["type"] == EntityType.NOTE + assert metadata["type"] == "note" # Verify content changed to note style (direct content) assert "# Test Note" in content @@ -294,4 +282,4 @@ async def test_update_entity_type_knowledge_to_note(knowledge_service: Knowledge assert "Test observation" not in content # Observations not included in note format # Verify metadata was preserved - assert metadata["status"] == "draft" \ No newline at end of file + assert metadata["status"] == "draft" diff --git a/tests/services/test_relation_service.py b/tests/services/test_relation_service.py index 030517ce..d8bfbe18 100644 --- a/tests/services/test_relation_service.py +++ b/tests/services/test_relation_service.py @@ -5,12 +5,9 @@ import pytest_asyncio from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from basic_memory.models import Entity, Relation -from basic_memory.models.knowledge import EntityType -from basic_memory.repository.relation_repository import RelationRepository from basic_memory.services.relation_service import RelationService - @pytest_asyncio.fixture async def test_entities( session_maker: async_sessionmaker[AsyncSession], @@ -19,17 +16,19 @@ async def test_entities( async with session_maker() as session: entity1 = Entity( name="test_entity_1", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", path_id="test/test_entity_1", file_path="test/test_entity_1.md", - description="Test entity 1", + summary="Test entity 1", + content_type="text/markdown", ) entity2 = Entity( name="test_entity_2", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", path_id="test/test_entity_2", file_path="test/test_entity_2.md", - description="Test entity 2", + summary="Test entity 2", + content_type="text/markdown", ) session.add_all([entity1, entity2]) await session.commit() diff --git a/tests/services/test_search_service.py b/tests/services/test_search_service.py index f43ea5c5..bf10381c 100644 --- a/tests/services/test_search_service.py +++ b/tests/services/test_search_service.py @@ -19,7 +19,8 @@ def test_entity(): entity_metadata = { "test": "test"} path_id = "component/test_component" file_path = "entities/component/test_component.md" - description = "A test component for search" + summary = "A test component for search" + content_type = "text/markdown" created_at = datetime.now(timezone.utc) updated_at = datetime.now(timezone.utc) observations = [] @@ -97,7 +98,7 @@ async def test_update_index(search_service, test_entity): await search_service.index_entity(test_entity) # Update entity - test_entity.description = "Updated description with new terms" + test_entity.summary = "Updated description with new terms" await search_service.index_entity(test_entity) # Search for new terms diff --git a/tests/sync/test_knowledge_sync_service.py b/tests/sync/test_knowledge_sync_service.py index 4e993bbf..99847dc1 100644 --- a/tests/sync/test_knowledge_sync_service.py +++ b/tests/sync/test_knowledge_sync_service.py @@ -14,7 +14,6 @@ from basic_memory.markdown.schemas import ( Relation as MarkdownRelation, ) from basic_memory.models import Entity as EntityModel -from basic_memory.models.knowledge import EntityType from basic_memory.sync.knowledge_sync_service import KnowledgeSyncService @@ -35,7 +34,7 @@ def test_content() -> EntityContent: """Create test content with observations and relations.""" return EntityContent( title="Test Entity", - description="A test entity description", + summary="A test entity description", observations=[ MarkdownObservation(content="First observation"), MarkdownObservation(content="Second observation"), @@ -65,7 +64,7 @@ async def test_create_entity_without_relations( # Check basic fields assert entity.name == "Test Entity" - assert entity.entity_type == EntityType.KNOWLEDGE + assert entity.entity_type == "knowledge" assert entity.path_id == "concept/test_entity" assert entity.summary == "A test entity description" @@ -91,7 +90,7 @@ async def test_update_entity_without_relations( # Modify markdown content test_markdown.content.title = "Updated Title" - test_markdown.content.description = "Updated description" + test_markdown.content.summary = "Updated description" test_markdown.content.observations = [MarkdownObservation(content="Updated observation")] # Update entity @@ -120,15 +119,17 @@ async def test_update_entity_relations( # Create target entities that relations point to other_entity = EntityModel( name="Other Entity", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", path_id="concept/other_entity", file_path="concept/other_entity.md", + content_type="text/markdown", ) another_entity = EntityModel( name="Another Entity", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", path_id="concept/another_entity", file_path="concept/another_entity.md", + content_type="text/markdown", ) await knowledge_sync_service.entity_service.add(other_entity) await knowledge_sync_service.entity_service.add(another_entity) @@ -163,15 +164,17 @@ async def test_two_pass_sync_flow( # Create target entities first other_entity = EntityModel( name="Other Entity", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", path_id="concept/other_entity", file_path="concept/other_entity.md", + content_type="text/markdown", ) another_entity = EntityModel( name="Another Entity", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", path_id="concept/another_entity", file_path="concept/another_entity.md", + content_type="text/markdown", ) await knowledge_sync_service.entity_service.add(other_entity) await knowledge_sync_service.entity_service.add(another_entity) diff --git a/tests/sync/test_sync_knowledge.py b/tests/sync/test_sync_knowledge.py index cd720fd0..5be7244f 100644 --- a/tests/sync/test_sync_knowledge.py +++ b/tests/sync/test_sync_knowledge.py @@ -6,7 +6,6 @@ import pytest from basic_memory.config import ProjectConfig from basic_memory.models import Entity -from basic_memory.models.knowledge import EntityType from basic_memory.services import EntityService from basic_memory.sync.sync_service import SyncService @@ -49,9 +48,10 @@ A test concept. other = Entity( path_id="concept/other", name="Other", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", file_path="concept/other.md", checksum="12345678", + content_type="text/markdown", ) await entity_service.repository.add(other) @@ -64,7 +64,7 @@ A test concept. # Find new entity test_concept = next(e for e in entities if e.path_id == "concept/test_concept") - assert test_concept.entity_type == EntityType.KNOWLEDGE + assert test_concept.entity_type == "knowledge" # Verify relation was not created # because file for related entity was not found diff --git a/tests/sync/test_sync_service.py b/tests/sync/test_sync_service.py index 4392f26a..855e1254 100644 --- a/tests/sync/test_sync_service.py +++ b/tests/sync/test_sync_service.py @@ -7,7 +7,6 @@ import pytest from basic_memory.config import ProjectConfig from basic_memory.models import Entity -from basic_memory.models.knowledge import EntityType from basic_memory.services import EntityService from basic_memory.sync.sync_service import SyncService @@ -34,7 +33,9 @@ async def test_sync_file_modified_during_sync( """Test handling of files that change during sync process.""" # Create initial files doc_path = test_config.knowledge_dir / "changing.md" - await create_test_file(doc_path, """ + await create_test_file( + doc_path, + """ --- type: knowledge id: changing @@ -45,7 +46,8 @@ modified: 2024-01-01 ## Observations - This is a test -""") +""", + ) # Setup async modification during sync async def modify_file(): @@ -71,9 +73,10 @@ async def test_sync_null_checksum_cleanup( entity = Entity( path_id="concept/incomplete", name="Incomplete", - entity_type=EntityType.KNOWLEDGE, + entity_type="test", file_path="concept/incomplete.md", checksum=None, # Null checksum + content_type="text/markdown", ) await entity_service.repository.add(entity) @@ -98,6 +101,3 @@ modified: 2024-01-01 # Verify entity was properly synced updated = await entity_service.get_by_path_id("concept/incomplete") assert updated.checksum is not None - - -