forked from cardosofelipe/fast-next-template
Implements Sub-Issue #87 of Issue #62 (Agent Memory System). Core infrastructure: - memory/types.py: Type definitions for all memory types (Working, Episodic, Semantic, Procedural) with enums for MemoryType, ScopeLevel, Outcome - memory/config.py: MemorySettings with MEM_ env prefix, thread-safe singleton - memory/exceptions.py: Comprehensive exception hierarchy for memory operations - memory/manager.py: MemoryManager facade with placeholder methods Directory structure: - working/: Working memory (Redis/in-memory) - to be implemented in #89 - episodic/: Episodic memory (experiences) - to be implemented in #90 - semantic/: Semantic memory (facts) - to be implemented in #91 - procedural/: Procedural memory (skills) - to be implemented in #92 - scoping/: Scope management - to be implemented in #93 - indexing/: Vector indexing - to be implemented in #94 - consolidation/: Memory consolidation - to be implemented in #95 Tests: 71 unit tests for config, types, and exceptions Docs: Comprehensive implementation plan at docs/architecture/memory-system-plan.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
326 lines
9.5 KiB
Python
326 lines
9.5 KiB
Python
"""
|
|
Tests for Memory System Exceptions.
|
|
"""
|
|
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from app.services.memory.exceptions import (
|
|
CheckpointError,
|
|
EmbeddingError,
|
|
MemoryCapacityError,
|
|
MemoryConflictError,
|
|
MemoryConsolidationError,
|
|
MemoryError,
|
|
MemoryExpiredError,
|
|
MemoryNotFoundError,
|
|
MemoryRetrievalError,
|
|
MemoryScopeError,
|
|
MemorySerializationError,
|
|
MemoryStorageError,
|
|
)
|
|
|
|
|
|
class TestMemoryError:
|
|
"""Tests for base MemoryError class."""
|
|
|
|
def test_basic_error(self) -> None:
|
|
"""Test creating a basic memory error."""
|
|
error = MemoryError("Something went wrong")
|
|
|
|
assert str(error) == "Something went wrong"
|
|
assert error.message == "Something went wrong"
|
|
assert error.memory_type is None
|
|
assert error.scope_type is None
|
|
assert error.scope_id is None
|
|
assert error.details == {}
|
|
|
|
def test_error_with_context(self) -> None:
|
|
"""Test creating an error with context."""
|
|
error = MemoryError(
|
|
"Operation failed",
|
|
memory_type="episodic",
|
|
scope_type="project",
|
|
scope_id="proj-123",
|
|
details={"operation": "search"},
|
|
)
|
|
|
|
assert error.memory_type == "episodic"
|
|
assert error.scope_type == "project"
|
|
assert error.scope_id == "proj-123"
|
|
assert error.details == {"operation": "search"}
|
|
|
|
def test_error_inheritance(self) -> None:
|
|
"""Test that MemoryError inherits from Exception."""
|
|
error = MemoryError("test")
|
|
assert isinstance(error, Exception)
|
|
|
|
|
|
class TestMemoryNotFoundError:
|
|
"""Tests for MemoryNotFoundError class."""
|
|
|
|
def test_default_message(self) -> None:
|
|
"""Test default error message."""
|
|
error = MemoryNotFoundError()
|
|
assert error.message == "Memory not found"
|
|
|
|
def test_with_memory_id(self) -> None:
|
|
"""Test error with memory ID."""
|
|
memory_id = uuid4()
|
|
error = MemoryNotFoundError(
|
|
f"Memory {memory_id} not found",
|
|
memory_id=memory_id,
|
|
)
|
|
|
|
assert error.memory_id == memory_id
|
|
|
|
def test_with_key(self) -> None:
|
|
"""Test error with key."""
|
|
error = MemoryNotFoundError(
|
|
"Key not found",
|
|
key="my_key",
|
|
)
|
|
|
|
assert error.key == "my_key"
|
|
|
|
|
|
class TestMemoryCapacityError:
|
|
"""Tests for MemoryCapacityError class."""
|
|
|
|
def test_default_message(self) -> None:
|
|
"""Test default error message."""
|
|
error = MemoryCapacityError()
|
|
assert error.message == "Memory capacity exceeded"
|
|
|
|
def test_with_sizes(self) -> None:
|
|
"""Test error with size information."""
|
|
error = MemoryCapacityError(
|
|
"Working memory full",
|
|
current_size=1048576,
|
|
max_size=1000000,
|
|
item_count=500,
|
|
)
|
|
|
|
assert error.current_size == 1048576
|
|
assert error.max_size == 1000000
|
|
assert error.item_count == 500
|
|
|
|
|
|
class TestMemoryExpiredError:
|
|
"""Tests for MemoryExpiredError class."""
|
|
|
|
def test_default_message(self) -> None:
|
|
"""Test default error message."""
|
|
error = MemoryExpiredError()
|
|
assert error.message == "Memory has expired"
|
|
|
|
def test_with_expiry_info(self) -> None:
|
|
"""Test error with expiry information."""
|
|
error = MemoryExpiredError(
|
|
"Key expired",
|
|
key="session_data",
|
|
expired_at="2025-01-05T00:00:00Z",
|
|
)
|
|
|
|
assert error.key == "session_data"
|
|
assert error.expired_at == "2025-01-05T00:00:00Z"
|
|
|
|
|
|
class TestMemoryStorageError:
|
|
"""Tests for MemoryStorageError class."""
|
|
|
|
def test_default_message(self) -> None:
|
|
"""Test default error message."""
|
|
error = MemoryStorageError()
|
|
assert error.message == "Memory storage operation failed"
|
|
|
|
def test_with_operation_info(self) -> None:
|
|
"""Test error with operation information."""
|
|
error = MemoryStorageError(
|
|
"Redis write failed",
|
|
operation="set",
|
|
backend="redis",
|
|
)
|
|
|
|
assert error.operation == "set"
|
|
assert error.backend == "redis"
|
|
|
|
|
|
class TestMemorySerializationError:
|
|
"""Tests for MemorySerializationError class."""
|
|
|
|
def test_default_message(self) -> None:
|
|
"""Test default error message."""
|
|
error = MemorySerializationError()
|
|
assert error.message == "Memory serialization failed"
|
|
|
|
def test_with_content_type(self) -> None:
|
|
"""Test error with content type."""
|
|
error = MemorySerializationError(
|
|
"Cannot serialize function",
|
|
content_type="function",
|
|
)
|
|
|
|
assert error.content_type == "function"
|
|
|
|
|
|
class TestMemoryScopeError:
|
|
"""Tests for MemoryScopeError class."""
|
|
|
|
def test_default_message(self) -> None:
|
|
"""Test default error message."""
|
|
error = MemoryScopeError()
|
|
assert error.message == "Memory scope error"
|
|
|
|
def test_with_scope_info(self) -> None:
|
|
"""Test error with scope information."""
|
|
error = MemoryScopeError(
|
|
"Scope access denied",
|
|
requested_scope="global",
|
|
allowed_scopes=["project", "session"],
|
|
)
|
|
|
|
assert error.requested_scope == "global"
|
|
assert error.allowed_scopes == ["project", "session"]
|
|
|
|
|
|
class TestMemoryConsolidationError:
|
|
"""Tests for MemoryConsolidationError class."""
|
|
|
|
def test_default_message(self) -> None:
|
|
"""Test default error message."""
|
|
error = MemoryConsolidationError()
|
|
assert error.message == "Memory consolidation failed"
|
|
|
|
def test_with_consolidation_info(self) -> None:
|
|
"""Test error with consolidation information."""
|
|
error = MemoryConsolidationError(
|
|
"Transfer failed",
|
|
source_type="working",
|
|
target_type="episodic",
|
|
items_processed=50,
|
|
)
|
|
|
|
assert error.source_type == "working"
|
|
assert error.target_type == "episodic"
|
|
assert error.items_processed == 50
|
|
|
|
|
|
class TestMemoryRetrievalError:
|
|
"""Tests for MemoryRetrievalError class."""
|
|
|
|
def test_default_message(self) -> None:
|
|
"""Test default error message."""
|
|
error = MemoryRetrievalError()
|
|
assert error.message == "Memory retrieval failed"
|
|
|
|
def test_with_query_info(self) -> None:
|
|
"""Test error with query information."""
|
|
error = MemoryRetrievalError(
|
|
"Search timeout",
|
|
query="complex search query",
|
|
retrieval_type="semantic",
|
|
)
|
|
|
|
assert error.query == "complex search query"
|
|
assert error.retrieval_type == "semantic"
|
|
|
|
|
|
class TestEmbeddingError:
|
|
"""Tests for EmbeddingError class."""
|
|
|
|
def test_default_message(self) -> None:
|
|
"""Test default error message."""
|
|
error = EmbeddingError()
|
|
assert error.message == "Embedding generation failed"
|
|
|
|
def test_with_embedding_info(self) -> None:
|
|
"""Test error with embedding information."""
|
|
error = EmbeddingError(
|
|
"Content too long",
|
|
content_length=100000,
|
|
model="text-embedding-3-small",
|
|
)
|
|
|
|
assert error.content_length == 100000
|
|
assert error.model == "text-embedding-3-small"
|
|
|
|
|
|
class TestCheckpointError:
|
|
"""Tests for CheckpointError class."""
|
|
|
|
def test_default_message(self) -> None:
|
|
"""Test default error message."""
|
|
error = CheckpointError()
|
|
assert error.message == "Checkpoint operation failed"
|
|
|
|
def test_with_checkpoint_info(self) -> None:
|
|
"""Test error with checkpoint information."""
|
|
error = CheckpointError(
|
|
"Restore failed",
|
|
checkpoint_id="chk-123",
|
|
operation="restore",
|
|
)
|
|
|
|
assert error.checkpoint_id == "chk-123"
|
|
assert error.operation == "restore"
|
|
|
|
|
|
class TestMemoryConflictError:
|
|
"""Tests for MemoryConflictError class."""
|
|
|
|
def test_default_message(self) -> None:
|
|
"""Test default error message."""
|
|
error = MemoryConflictError()
|
|
assert error.message == "Memory conflict detected"
|
|
|
|
def test_with_conflict_info(self) -> None:
|
|
"""Test error with conflict information."""
|
|
id1 = uuid4()
|
|
id2 = uuid4()
|
|
error = MemoryConflictError(
|
|
"Contradictory facts detected",
|
|
conflicting_ids=[id1, id2],
|
|
conflict_type="semantic",
|
|
)
|
|
|
|
assert len(error.conflicting_ids) == 2
|
|
assert error.conflict_type == "semantic"
|
|
|
|
|
|
class TestExceptionHierarchy:
|
|
"""Tests for exception inheritance hierarchy."""
|
|
|
|
def test_all_exceptions_inherit_from_memory_error(self) -> None:
|
|
"""Test that all exceptions inherit from MemoryError."""
|
|
exceptions = [
|
|
MemoryNotFoundError(),
|
|
MemoryCapacityError(),
|
|
MemoryExpiredError(),
|
|
MemoryStorageError(),
|
|
MemorySerializationError(),
|
|
MemoryScopeError(),
|
|
MemoryConsolidationError(),
|
|
MemoryRetrievalError(),
|
|
EmbeddingError(),
|
|
CheckpointError(),
|
|
MemoryConflictError(),
|
|
]
|
|
|
|
for exc in exceptions:
|
|
assert isinstance(exc, MemoryError)
|
|
assert isinstance(exc, Exception)
|
|
|
|
def test_can_catch_base_error(self) -> None:
|
|
"""Test that catching MemoryError catches all subclasses."""
|
|
exceptions = [
|
|
MemoryNotFoundError("not found"),
|
|
MemoryCapacityError("capacity"),
|
|
MemoryStorageError("storage"),
|
|
]
|
|
|
|
for exc in exceptions:
|
|
with pytest.raises(MemoryError):
|
|
raise exc
|