Files
basicmachines-co-basic-memory/tests/schemas/test_schemas.py
T
2024-12-24 22:25:06 -06:00

240 lines
8.0 KiB
Python

"""Tests for Pydantic schema validation and conversion."""
import pytest
from pydantic import ValidationError
from basic_memory.schemas import (
Entity,
EntityResponse,
Relation,
CreateEntityRequest,
SearchNodesRequest,
OpenNodesRequest, RelationResponse,
)
from basic_memory.schemas.base import to_snake_case
def test_entity_in_minimal():
"""Test creating EntityIn with minimal required fields."""
data = {"name": "test_entity", "entity_type": "test"}
entity = Entity.model_validate(data)
assert entity.name == "test_entity"
assert entity.entity_type == "test"
assert entity.description is None
assert entity.observations == []
def test_entity_in_complete():
"""Test creating EntityIn with all fields."""
data = {
"name": "test_entity",
"entity_type": "test",
"description": "A test entity",
"observations": ["Test observation"],
}
entity = Entity.model_validate(data)
assert entity.name == "test_entity"
assert entity.entity_type == "test"
assert entity.description == "A test entity"
assert len(entity.observations) == 1
assert entity.observations[0] == "Test observation"
def test_entity_in_validation():
"""Test validation errors for EntityIn."""
with pytest.raises(ValidationError):
Entity.model_validate({}) # Missing required fields
with pytest.raises(ValidationError):
Entity.model_validate({"name": "test"}) # Missing entityType
with pytest.raises(ValidationError):
Entity.model_validate({"entityType": "test"}) # Missing name
def test_relation_in_validation():
"""Test RelationIn validation."""
data = {"from_id": "test/123", "to_id": "test/456", "relation_type": "test"}
relation = Relation.model_validate(data)
assert relation.from_id == "test/123"
assert relation.to_id == "test/456"
assert relation.relation_type == "test"
assert relation.context is None
# With context
data["context"] = "test context"
relation = Relation.model_validate(data)
assert relation.context == "test context"
# Missing required fields
with pytest.raises(ValidationError):
Relation.model_validate({"from_id": "123", "to_id": "456"}) # Missing relationType
def test_relation_response():
"""Test RelationResponse validation."""
data = {"from_id": "test/123", "to_id": "test/456", "relation_type": "test", "from_entity":{"path_id": "test/123"}, "to_entity":{"path_id": "test/456"}}
relation = RelationResponse.model_validate(data)
assert relation.from_id == "test/123"
assert relation.to_id == "test/456"
assert relation.relation_type == "test"
assert relation.context is None
def test_create_entities_input():
"""Test CreateEntitiesInput validation."""
data = {
"entities": [
{"name": "entity1", "entity_type": "test"},
{"name": "entity2", "entity_type": "test", "description": "test description"},
]
}
create_input = CreateEntityRequest.model_validate(data)
assert len(create_input.entities) == 2
assert create_input.entities[1].description == "test description"
# Empty entities list should fail
with pytest.raises(ValidationError):
CreateEntityRequest.model_validate({"entities": []})
def test_entity_out_from_attributes():
"""Test EntityOut creation from database model attributes."""
# Simulate database model attributes
db_data = {
"path_id": "test/test",
"name": "test",
"entity_type": "test",
"description": "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}
],
}
entity = EntityResponse.model_validate(db_data)
assert entity.path_id == "test/test"
assert entity.description == "test description"
assert len(entity.observations) == 1
assert len(entity.relations) == 1
def test_optional_fields():
"""Test handling of optional fields."""
# Create with no optional fields
entity = Entity.model_validate({"name": "test", "entity_type": "test"})
assert entity.description is None
assert entity.observations == []
# Create with empty optional fields
entity = Entity.model_validate(
{
"name": "test",
"entity_type": "test",
"description": None,
"observations": [],
}
)
assert entity.description is None
assert entity.observations == []
# Create with some optional fields
entity = Entity.model_validate(
{"name": "test", "entity_type": "test", "description": "test", "observations": []}
)
assert entity.description == "test"
assert entity.observations == []
def test_search_nodes_input():
"""Test SearchNodesInput validation."""
search = SearchNodesRequest.model_validate({"query": "test query"})
assert search.query == "test query"
with pytest.raises(ValidationError):
SearchNodesRequest.model_validate({}) # Missing required query
def test_open_nodes_input():
"""Test OpenNodesInput validation."""
open_input = OpenNodesRequest.model_validate({"path_ids": ["test/test", "test/test2"]})
assert len(open_input.path_ids) == 2
# Empty names list should fail
with pytest.raises(ValidationError):
OpenNodesRequest.model_validate({"path_ids": []})
def test_path_sanitization():
"""Test to_snake_case() handles various inputs correctly."""
test_cases = [
("BasicMemory", "basic_memory"), # CamelCase
("Memory Service", "memory_service"), # Spaces
("memory-service", "memory_service"), # Hyphens
("Memory_Service", "memory_service"), # Already has underscore
("API2Service", "api2_service"), # Numbers
(" Spaces ", "spaces"), # Extra spaces
("mixedCase", "mixed_case"), # Mixed case
("snake_case_already", "snake_case_already"), # Already snake case
("ALLCAPS", "allcaps"), # All caps
("with.dots", "with_dots"), # Dots
]
for input_str, expected in test_cases:
result = to_snake_case(input_str)
assert result == expected, f"Failed for input: {input_str}"
def test_path_id_generation():
"""Test path_id property generates correct paths."""
test_cases = [
(
{"name": "BasicMemory", "entity_type": "Project"},
"project/basic_memory"
),
(
{"name": "Memory Service", "entity_type": "Component"},
"component/memory_service"
),
(
{"name": "API Gateway", "entity_type": "Service"},
"service/api_gateway"
),
(
{"name": "TestCase1", "entity_type": "Test"},
"test/test_case1"
),
]
for input_data, expected_path in test_cases:
entity = Entity.model_validate(input_data)
assert entity.path_id == expected_path, f"Failed for input: {input_data}"
# def test_path_id_validation():
# """Test path ID format validation."""
# valid_paths = [
# "project/basic_memory",
# "test/test_case_1",
# "component/api_gateway",
# ]
#
# invalid_paths = [
# "no_separator", # Missing /
# "/missing_type", # Missing type
# "type/", # Missing name
# "type//double", # Double separator
# "../path/traversal", # Path traversal attempt
# "type/name/extra", # Too many parts
# "", # Empty string
# ]
#
# # Test valid paths
# for path in valid_paths:
# try:
# Relation.model_validate({"from_id": path, "to_id": path, "relation_type": "test"})
# except ValidationError as e:
# assert False, f"Valid path {path} failed validation: {e}"
#
# # Test invalid paths
# for path in invalid_paths:
# with pytest.raises(ValidationError):
# Relation.model_validate({"from_id": path, "to_id": "test/valid", "relation_type": "test"})