forked from cardosofelipe/fast-next-template
Moved tests/unit/models/memory/ to tests/models/memory/ to avoid Python import path conflicts when pytest collects all tests. The conflict was caused by tests/models/ and tests/unit/models/ both having __init__.py files, causing Python to confuse app.models.memory imports. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
250 lines
7.8 KiB
Python
250 lines
7.8 KiB
Python
# 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"
|