# tests/unit/models/memory/test_models.py """Unit tests for memory database models.""" from datetime import UTC, datetime, timedelta import pytest from app.models.memory import ( ConsolidationStatus, ConsolidationType, Episode, EpisodeOutcome, Fact, MemoryConsolidationLog, Procedure, ScopeType, WorkingMemory, ) class TestWorkingMemoryModel: """Tests for WorkingMemory model.""" def test_tablename(self) -> None: """Test table name is correct.""" assert WorkingMemory.__tablename__ == "working_memory" def test_has_required_columns(self) -> None: """Test all required columns exist.""" columns = WorkingMemory.__table__.columns assert "id" in columns assert "scope_type" in columns assert "scope_id" in columns assert "key" in columns assert "value" in columns assert "expires_at" in columns assert "created_at" in columns assert "updated_at" in columns def test_has_unique_constraint(self) -> None: """Test unique constraint on scope+key.""" indexes = {idx.name: idx for idx in WorkingMemory.__table__.indexes} assert "ix_working_memory_scope_key" in indexes assert indexes["ix_working_memory_scope_key"].unique class TestEpisodeModel: """Tests for Episode model.""" def test_tablename(self) -> None: """Test table name is correct.""" assert Episode.__tablename__ == "episodes" def test_has_required_columns(self) -> None: """Test all required columns exist.""" columns = Episode.__table__.columns required = [ "id", "project_id", "agent_instance_id", "agent_type_id", "session_id", "task_type", "task_description", "actions", "context_summary", "outcome", "outcome_details", "duration_seconds", "tokens_used", "lessons_learned", "importance_score", "embedding", "occurred_at", "created_at", "updated_at", ] for col in required: assert col in columns, f"Missing column: {col}" def test_has_foreign_keys(self) -> None: """Test foreign key relationships exist.""" columns = Episode.__table__.columns assert columns["project_id"].foreign_keys assert columns["agent_instance_id"].foreign_keys assert columns["agent_type_id"].foreign_keys def test_has_relationships(self) -> None: """Test ORM relationships exist.""" mapper = Episode.__mapper__ assert "project" in mapper.relationships assert "agent_instance" in mapper.relationships assert "agent_type" in mapper.relationships class TestFactModel: """Tests for Fact model.""" def test_tablename(self) -> None: """Test table name is correct.""" assert Fact.__tablename__ == "facts" def test_has_required_columns(self) -> None: """Test all required columns exist.""" columns = Fact.__table__.columns required = [ "id", "project_id", "subject", "predicate", "object", "confidence", "source_episode_ids", "first_learned", "last_reinforced", "reinforcement_count", "embedding", "created_at", "updated_at", ] for col in required: assert col in columns, f"Missing column: {col}" def test_project_id_nullable(self) -> None: """Test project_id is nullable for global facts.""" columns = Fact.__table__.columns assert columns["project_id"].nullable class TestProcedureModel: """Tests for Procedure model.""" def test_tablename(self) -> None: """Test table name is correct.""" assert Procedure.__tablename__ == "procedures" def test_has_required_columns(self) -> None: """Test all required columns exist.""" columns = Procedure.__table__.columns required = [ "id", "project_id", "agent_type_id", "name", "trigger_pattern", "steps", "success_count", "failure_count", "last_used", "embedding", "created_at", "updated_at", ] for col in required: assert col in columns, f"Missing column: {col}" def test_success_rate_property(self) -> None: """Test success_rate calculated property.""" proc = Procedure() proc.success_count = 8 proc.failure_count = 2 assert proc.success_rate == 0.8 def test_success_rate_zero_total(self) -> None: """Test success_rate with zero total uses.""" proc = Procedure() proc.success_count = 0 proc.failure_count = 0 assert proc.success_rate == 0.0 def test_total_uses_property(self) -> None: """Test total_uses calculated property.""" proc = Procedure() proc.success_count = 5 proc.failure_count = 3 assert proc.total_uses == 8 class TestMemoryConsolidationLogModel: """Tests for MemoryConsolidationLog model.""" def test_tablename(self) -> None: """Test table name is correct.""" assert MemoryConsolidationLog.__tablename__ == "memory_consolidation_log" def test_has_required_columns(self) -> None: """Test all required columns exist.""" columns = MemoryConsolidationLog.__table__.columns required = [ "id", "consolidation_type", "source_count", "result_count", "started_at", "completed_at", "status", "error", "created_at", "updated_at", ] for col in required: assert col in columns, f"Missing column: {col}" def test_duration_seconds_property_completed(self) -> None: """Test duration_seconds with completed job.""" log = MemoryConsolidationLog() log.started_at = datetime.now(UTC) log.completed_at = log.started_at + timedelta(seconds=10) assert log.duration_seconds == pytest.approx(10.0) def test_duration_seconds_property_incomplete(self) -> None: """Test duration_seconds with incomplete job.""" log = MemoryConsolidationLog() log.started_at = datetime.now(UTC) log.completed_at = None assert log.duration_seconds is None def test_default_status(self) -> None: """Test default status is PENDING.""" columns = MemoryConsolidationLog.__table__.columns assert columns["status"].default.arg == ConsolidationStatus.PENDING class TestModelExports: """Tests for model package exports.""" def test_all_models_exported(self) -> None: """Test all models are exported from package.""" from app.models.memory import ( Episode, Fact, MemoryConsolidationLog, Procedure, WorkingMemory, ) # Verify these are the actual classes assert Episode.__tablename__ == "episodes" assert Fact.__tablename__ == "facts" assert Procedure.__tablename__ == "procedures" assert WorkingMemory.__tablename__ == "working_memory" assert MemoryConsolidationLog.__tablename__ == "memory_consolidation_log" def test_enums_exported(self) -> None: """Test all enums are exported.""" assert ScopeType.GLOBAL.value == "global" assert EpisodeOutcome.SUCCESS.value == "success" assert ConsolidationType.WORKING_TO_EPISODIC.value == "working_to_episodic" assert ConsolidationStatus.PENDING.value == "pending"