Files
syndarix/backend/tests/unit/services/memory/test_config.py
Felipe Cardoso 085a748929 feat(memory): #87 project setup & core architecture
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>
2026-01-05 01:27:36 +01:00

244 lines
8.5 KiB
Python

"""
Tests for Memory System Configuration.
"""
import pytest
from pydantic import ValidationError
from app.services.memory.config import (
MemorySettings,
get_default_settings,
get_memory_settings,
reset_memory_settings,
)
class TestMemorySettings:
"""Tests for MemorySettings class."""
def test_default_settings(self) -> None:
"""Test that default settings are valid."""
settings = MemorySettings()
# Working memory defaults
assert settings.working_memory_backend == "redis"
assert settings.working_memory_default_ttl_seconds == 3600
assert settings.working_memory_max_items_per_session == 1000
# Redis defaults
assert settings.redis_url == "redis://localhost:6379/0"
assert settings.redis_prefix == "mem"
# Episodic defaults
assert settings.episodic_max_episodes_per_project == 10000
assert settings.episodic_default_importance == 0.5
# Semantic defaults
assert settings.semantic_max_facts_per_project == 50000
assert settings.semantic_min_confidence == 0.1
# Procedural defaults
assert settings.procedural_max_procedures_per_project == 1000
assert settings.procedural_min_success_rate == 0.3
# Embedding defaults
assert settings.embedding_model == "text-embedding-3-small"
assert settings.embedding_dimensions == 1536
# Retrieval defaults
assert settings.retrieval_default_limit == 10
assert settings.retrieval_max_limit == 100
def test_invalid_backend(self) -> None:
"""Test that invalid backend raises error."""
with pytest.raises(ValidationError) as exc_info:
MemorySettings(working_memory_backend="invalid")
assert "backend must be one of" in str(exc_info.value)
def test_valid_backends(self) -> None:
"""Test valid backend values."""
redis_settings = MemorySettings(working_memory_backend="redis")
assert redis_settings.working_memory_backend == "redis"
memory_settings = MemorySettings(working_memory_backend="memory")
assert memory_settings.working_memory_backend == "memory"
def test_invalid_embedding_model(self) -> None:
"""Test that invalid embedding model raises error."""
with pytest.raises(ValidationError) as exc_info:
MemorySettings(embedding_model="invalid-model")
assert "embedding_model must be one of" in str(exc_info.value)
def test_valid_embedding_models(self) -> None:
"""Test valid embedding model values."""
for model in [
"text-embedding-3-small",
"text-embedding-3-large",
"text-embedding-ada-002",
]:
settings = MemorySettings(embedding_model=model)
assert settings.embedding_model == model
def test_retrieval_limit_validation(self) -> None:
"""Test that default limit cannot exceed max limit."""
with pytest.raises(ValidationError) as exc_info:
MemorySettings(
retrieval_default_limit=50,
retrieval_max_limit=25,
)
assert "cannot exceed retrieval_max_limit" in str(exc_info.value)
def test_valid_retrieval_limits(self) -> None:
"""Test valid retrieval limit combinations."""
settings = MemorySettings(
retrieval_default_limit=10,
retrieval_max_limit=50,
)
assert settings.retrieval_default_limit == 10
assert settings.retrieval_max_limit == 50
# Equal limits should be valid
settings = MemorySettings(
retrieval_default_limit=25,
retrieval_max_limit=25,
)
assert settings.retrieval_default_limit == 25
assert settings.retrieval_max_limit == 25
def test_ttl_bounds(self) -> None:
"""Test TTL setting bounds."""
# Valid TTL
settings = MemorySettings(working_memory_default_ttl_seconds=1800)
assert settings.working_memory_default_ttl_seconds == 1800
# Too low
with pytest.raises(ValidationError):
MemorySettings(working_memory_default_ttl_seconds=30)
# Too high
with pytest.raises(ValidationError):
MemorySettings(working_memory_default_ttl_seconds=100000)
def test_confidence_bounds(self) -> None:
"""Test confidence score bounds."""
# Valid confidence
settings = MemorySettings(semantic_min_confidence=0.5)
assert settings.semantic_min_confidence == 0.5
# Bounds
settings = MemorySettings(semantic_min_confidence=0.0)
assert settings.semantic_min_confidence == 0.0
settings = MemorySettings(semantic_min_confidence=1.0)
assert settings.semantic_min_confidence == 1.0
# Out of bounds
with pytest.raises(ValidationError):
MemorySettings(semantic_min_confidence=-0.1)
with pytest.raises(ValidationError):
MemorySettings(semantic_min_confidence=1.1)
def test_get_working_memory_config(self) -> None:
"""Test working memory config dictionary."""
settings = MemorySettings()
config = settings.get_working_memory_config()
assert config["backend"] == "redis"
assert config["default_ttl_seconds"] == 3600
assert config["max_items_per_session"] == 1000
assert config["max_value_size_bytes"] == 1048576
assert config["checkpoint_enabled"] is True
def test_get_redis_config(self) -> None:
"""Test Redis config dictionary."""
settings = MemorySettings()
config = settings.get_redis_config()
assert config["url"] == "redis://localhost:6379/0"
assert config["prefix"] == "mem"
assert config["connection_timeout_seconds"] == 5
def test_get_embedding_config(self) -> None:
"""Test embedding config dictionary."""
settings = MemorySettings()
config = settings.get_embedding_config()
assert config["model"] == "text-embedding-3-small"
assert config["dimensions"] == 1536
assert config["batch_size"] == 100
assert config["cache_enabled"] is True
def test_get_consolidation_config(self) -> None:
"""Test consolidation config dictionary."""
settings = MemorySettings()
config = settings.get_consolidation_config()
assert config["enabled"] is True
assert config["batch_size"] == 100
assert config["schedule_cron"] == "0 3 * * *"
assert config["working_to_episodic_delay_minutes"] == 30
def test_to_dict(self) -> None:
"""Test full settings to dictionary."""
settings = MemorySettings()
config = settings.to_dict()
assert "working_memory" in config
assert "redis" in config
assert "episodic" in config
assert "semantic" in config
assert "procedural" in config
assert "embedding" in config
assert "retrieval" in config
assert "consolidation" in config
assert "pruning" in config
assert "cache" in config
assert "performance" in config
class TestMemorySettingsSingleton:
"""Tests for MemorySettings singleton functions."""
def setup_method(self) -> None:
"""Reset singleton before each test."""
reset_memory_settings()
def teardown_method(self) -> None:
"""Reset singleton after each test."""
reset_memory_settings()
def test_get_memory_settings_singleton(self) -> None:
"""Test that get_memory_settings returns same instance."""
settings1 = get_memory_settings()
settings2 = get_memory_settings()
assert settings1 is settings2
def test_reset_memory_settings(self) -> None:
"""Test that reset creates new instance."""
settings1 = get_memory_settings()
reset_memory_settings()
settings2 = get_memory_settings()
assert settings1 is not settings2
def test_get_default_settings_cached(self) -> None:
"""Test that get_default_settings is cached."""
# Clear the lru_cache first
get_default_settings.cache_clear()
settings1 = get_default_settings()
settings2 = get_default_settings()
assert settings1 is settings2
def test_default_settings_immutable_pattern(self) -> None:
"""Test that default settings provide consistent values."""
defaults = get_default_settings()
assert defaults.working_memory_backend == "redis"
assert defaults.embedding_model == "text-embedding-3-small"