mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
add validation tests for observation schema
This commit is contained in:
@@ -69,7 +69,20 @@ def validate_path_format(path: str) -> str:
|
||||
return path
|
||||
|
||||
|
||||
|
||||
class ObservationCategory(str, Enum):
|
||||
"""Categories for structuring observations.
|
||||
|
||||
Categories help organize knowledge and make it easier to find later:
|
||||
- tech: Implementation details and technical notes
|
||||
- design: Architecture decisions and patterns
|
||||
- feature: User-facing capabilities
|
||||
- note: General observations (default)
|
||||
- issue: Problems or concerns
|
||||
- todo: Future work items
|
||||
|
||||
Categories are case-insensitive for easier use.
|
||||
"""
|
||||
TECH = "tech"
|
||||
DESIGN = "design"
|
||||
FEATURE = "feature"
|
||||
@@ -77,10 +90,24 @@ class ObservationCategory(str, Enum):
|
||||
ISSUE = "issue"
|
||||
TODO = "todo"
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value: str) -> "ObservationCategory":
|
||||
"""Handle case-insensitive lookup."""
|
||||
try:
|
||||
return cls(value.lower())
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
PathId = Annotated[str, BeforeValidator(to_snake_case), BeforeValidator(validate_path_format)]
|
||||
"""Unique identifier in format '{path}/{normalized_name}'."""
|
||||
|
||||
Observation = Annotated[str, MinLen(1), MaxLen(1000)]
|
||||
Observation = Annotated[
|
||||
str,
|
||||
BeforeValidator(str.strip), # Clean whitespace
|
||||
MinLen(1), # Ensure non-empty after stripping
|
||||
MaxLen(1000) # Keep reasonable length
|
||||
]
|
||||
"""A single piece of information about an entity. Must be non-empty and under 1000 characters.
|
||||
|
||||
Best Practices:
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
"""Tests for observation schema validation and categories."""
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from basic_memory.schemas import ObservationResponse
|
||||
from basic_memory.schemas.base import ObservationCategory
|
||||
from basic_memory.schemas.request import ObservationCreate
|
||||
|
||||
|
||||
def test_observation_create_minimal():
|
||||
"""Test creating ObservationCreate with minimal fields."""
|
||||
data = {"content": "Test observation"}
|
||||
obs = ObservationCreate.model_validate(data)
|
||||
assert obs.content == "Test observation"
|
||||
assert obs.category == ObservationCategory.NOTE # Default category
|
||||
|
||||
|
||||
def test_observation_create_complete():
|
||||
"""Test creating ObservationCreate with all fields."""
|
||||
data = {"content": "Test observation", "category": "tech", "context": "Test context"}
|
||||
obs = ObservationCreate.model_validate(data)
|
||||
assert obs.content == "Test observation"
|
||||
assert obs.category == ObservationCategory.TECH
|
||||
|
||||
|
||||
def test_observation_create_category_validation():
|
||||
"""Test category validation in ObservationCreate."""
|
||||
# Valid categories
|
||||
for category in ObservationCategory:
|
||||
data = {"content": "Test", "category": category.value}
|
||||
obs = ObservationCreate.model_validate(data)
|
||||
assert obs.category == category
|
||||
|
||||
# Invalid category
|
||||
with pytest.raises(ValidationError):
|
||||
ObservationCreate.model_validate({"content": "Test", "category": "invalid_category"})
|
||||
|
||||
|
||||
def test_observation_response():
|
||||
"""Test ObservationResponse validation and conversion."""
|
||||
data = {
|
||||
"id": 1,
|
||||
"path_id": 1,
|
||||
"content": "Test observation",
|
||||
"category": "tech",
|
||||
"context": "Test context",
|
||||
"created_at": "2024-12-25T12:00:00",
|
||||
"updated_at": "2024-12-25T12:00:00",
|
||||
}
|
||||
obs = ObservationResponse.model_validate(data)
|
||||
assert obs.content == "Test observation"
|
||||
assert obs.category == ObservationCategory.TECH
|
||||
assert obs.context == "Test context"
|
||||
|
||||
|
||||
def test_observation_create_empty_content():
|
||||
"""Test validation of empty content."""
|
||||
with pytest.raises(ValidationError):
|
||||
ObservationCreate.model_validate({"content": "", "category": "tech"})
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
ObservationCreate.model_validate(
|
||||
{
|
||||
"content": " ", # Just whitespace
|
||||
"category": "tech",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_observation_create_content_length():
|
||||
"""Test content length validation."""
|
||||
# Create string just over max length
|
||||
long_content = "x" * 1001
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
ObservationCreate.model_validate({"content": long_content, "category": "tech"})
|
||||
|
||||
|
||||
def test_observation_category_coercion():
|
||||
"""Test category accepts both string and enum."""
|
||||
# Test with string
|
||||
obs1 = ObservationCreate.model_validate({"content": "Test", "category": "tech"})
|
||||
assert obs1.category == ObservationCategory.TECH
|
||||
|
||||
# Test with enum
|
||||
obs2 = ObservationCreate.model_validate(
|
||||
{"content": "Test", "category": ObservationCategory.TECH}
|
||||
)
|
||||
assert obs2.category == ObservationCategory.TECH
|
||||
|
||||
# Both should be equal
|
||||
assert obs1.category == obs2.category
|
||||
|
||||
|
||||
def test_observation_category_case_insensitive():
|
||||
"""Test category validation is case insensitive."""
|
||||
variations = ["TECH", "tech", "Tech", "TEcH"]
|
||||
|
||||
for variant in variations:
|
||||
obs = ObservationCreate.model_validate({"content": "Test", "category": variant})
|
||||
assert obs.category == ObservationCategory.TECH
|
||||
Reference in New Issue
Block a user