feat(backend): Add Syndarix domain models with CRUD operations
- Add Project model with slug, description, autonomy level, and settings - Add AgentType model for agent templates with model config and failover - Add AgentInstance model for running agents with status and memory - Add Issue model with external tracker sync (Gitea/GitHub/GitLab) - Add Sprint model with velocity tracking and lifecycle management - Add comprehensive Pydantic schemas with validation - Add full CRUD operations for all models with filtering/sorting - Add 280+ tests for models, schemas, and CRUD operations Implements #23, #24, #25, #26, #27 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
244
backend/tests/schemas/syndarix/test_agent_instance_schemas.py
Normal file
244
backend/tests/schemas/syndarix/test_agent_instance_schemas.py
Normal file
@@ -0,0 +1,244 @@
|
||||
# tests/schemas/syndarix/test_agent_instance_schemas.py
|
||||
"""
|
||||
Tests for AgentInstance schema validation.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from app.schemas.syndarix import (
|
||||
AgentInstanceCreate,
|
||||
AgentInstanceUpdate,
|
||||
AgentStatus,
|
||||
)
|
||||
|
||||
|
||||
class TestAgentInstanceCreateValidation:
|
||||
"""Tests for AgentInstanceCreate schema validation."""
|
||||
|
||||
def test_valid_agent_instance_create(self, valid_agent_instance_data):
|
||||
"""Test creating agent instance with valid data."""
|
||||
instance = AgentInstanceCreate(**valid_agent_instance_data)
|
||||
|
||||
assert instance.agent_type_id is not None
|
||||
assert instance.project_id is not None
|
||||
|
||||
def test_agent_instance_create_defaults(self, valid_agent_instance_data):
|
||||
"""Test that defaults are applied correctly."""
|
||||
instance = AgentInstanceCreate(**valid_agent_instance_data)
|
||||
|
||||
assert instance.status == AgentStatus.IDLE
|
||||
assert instance.current_task is None
|
||||
assert instance.short_term_memory == {}
|
||||
assert instance.long_term_memory_ref is None
|
||||
assert instance.session_id is None
|
||||
|
||||
def test_agent_instance_create_with_all_fields(self, valid_uuid):
|
||||
"""Test creating agent instance with all optional fields."""
|
||||
instance = AgentInstanceCreate(
|
||||
agent_type_id=valid_uuid,
|
||||
project_id=valid_uuid,
|
||||
status=AgentStatus.WORKING,
|
||||
current_task="Processing feature request",
|
||||
short_term_memory={"context": "working"},
|
||||
long_term_memory_ref="project-123/agent-456",
|
||||
session_id="session-abc",
|
||||
)
|
||||
|
||||
assert instance.status == AgentStatus.WORKING
|
||||
assert instance.current_task == "Processing feature request"
|
||||
assert instance.short_term_memory == {"context": "working"}
|
||||
assert instance.long_term_memory_ref == "project-123/agent-456"
|
||||
assert instance.session_id == "session-abc"
|
||||
|
||||
def test_agent_instance_create_agent_type_id_required(self, valid_uuid):
|
||||
"""Test that agent_type_id is required."""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
AgentInstanceCreate(
|
||||
project_id=valid_uuid,
|
||||
)
|
||||
|
||||
errors = exc_info.value.errors()
|
||||
assert any("agent_type_id" in str(e).lower() for e in errors)
|
||||
|
||||
def test_agent_instance_create_project_id_required(self, valid_uuid):
|
||||
"""Test that project_id is required."""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
AgentInstanceCreate(
|
||||
agent_type_id=valid_uuid,
|
||||
)
|
||||
|
||||
errors = exc_info.value.errors()
|
||||
assert any("project_id" in str(e).lower() for e in errors)
|
||||
|
||||
|
||||
class TestAgentInstanceUpdateValidation:
|
||||
"""Tests for AgentInstanceUpdate schema validation."""
|
||||
|
||||
def test_agent_instance_update_partial(self):
|
||||
"""Test updating only some fields."""
|
||||
update = AgentInstanceUpdate(
|
||||
status=AgentStatus.WORKING,
|
||||
)
|
||||
|
||||
assert update.status == AgentStatus.WORKING
|
||||
assert update.current_task is None
|
||||
assert update.short_term_memory is None
|
||||
|
||||
def test_agent_instance_update_all_fields(self):
|
||||
"""Test updating all fields."""
|
||||
from datetime import UTC, datetime
|
||||
|
||||
now = datetime.now(UTC)
|
||||
update = AgentInstanceUpdate(
|
||||
status=AgentStatus.WORKING,
|
||||
current_task="New task",
|
||||
short_term_memory={"new": "context"},
|
||||
long_term_memory_ref="new-ref",
|
||||
session_id="new-session",
|
||||
last_activity_at=now,
|
||||
tasks_completed=5,
|
||||
tokens_used=10000,
|
||||
cost_incurred=Decimal("1.5000"),
|
||||
)
|
||||
|
||||
assert update.status == AgentStatus.WORKING
|
||||
assert update.current_task == "New task"
|
||||
assert update.tasks_completed == 5
|
||||
assert update.tokens_used == 10000
|
||||
assert update.cost_incurred == Decimal("1.5000")
|
||||
|
||||
def test_agent_instance_update_tasks_completed_negative_fails(self):
|
||||
"""Test that negative tasks_completed raises ValidationError."""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
AgentInstanceUpdate(tasks_completed=-1)
|
||||
|
||||
errors = exc_info.value.errors()
|
||||
assert any("tasks_completed" in str(e).lower() for e in errors)
|
||||
|
||||
def test_agent_instance_update_tokens_used_negative_fails(self):
|
||||
"""Test that negative tokens_used raises ValidationError."""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
AgentInstanceUpdate(tokens_used=-1)
|
||||
|
||||
errors = exc_info.value.errors()
|
||||
assert any("tokens_used" in str(e).lower() for e in errors)
|
||||
|
||||
def test_agent_instance_update_cost_incurred_negative_fails(self):
|
||||
"""Test that negative cost_incurred raises ValidationError."""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
AgentInstanceUpdate(cost_incurred=Decimal("-0.01"))
|
||||
|
||||
errors = exc_info.value.errors()
|
||||
assert any("cost_incurred" in str(e).lower() for e in errors)
|
||||
|
||||
|
||||
class TestAgentStatusEnum:
|
||||
"""Tests for AgentStatus enum validation."""
|
||||
|
||||
def test_valid_agent_statuses(self, valid_uuid):
|
||||
"""Test all valid agent statuses."""
|
||||
for status in AgentStatus:
|
||||
instance = AgentInstanceCreate(
|
||||
agent_type_id=valid_uuid,
|
||||
project_id=valid_uuid,
|
||||
status=status,
|
||||
)
|
||||
assert instance.status == status
|
||||
|
||||
def test_invalid_agent_status(self, valid_uuid):
|
||||
"""Test that invalid agent status raises ValidationError."""
|
||||
with pytest.raises(ValidationError):
|
||||
AgentInstanceCreate(
|
||||
agent_type_id=valid_uuid,
|
||||
project_id=valid_uuid,
|
||||
status="invalid", # type: ignore
|
||||
)
|
||||
|
||||
|
||||
class TestAgentInstanceShortTermMemory:
|
||||
"""Tests for AgentInstance short_term_memory validation."""
|
||||
|
||||
def test_short_term_memory_empty_dict(self, valid_uuid):
|
||||
"""Test that empty short_term_memory is valid."""
|
||||
instance = AgentInstanceCreate(
|
||||
agent_type_id=valid_uuid,
|
||||
project_id=valid_uuid,
|
||||
short_term_memory={},
|
||||
)
|
||||
assert instance.short_term_memory == {}
|
||||
|
||||
def test_short_term_memory_complex(self, valid_uuid):
|
||||
"""Test complex short_term_memory structure."""
|
||||
memory = {
|
||||
"conversation_history": [
|
||||
{"role": "user", "content": "Hello"},
|
||||
{"role": "assistant", "content": "Hi there"},
|
||||
],
|
||||
"recent_files": ["file1.py", "file2.py"],
|
||||
"decisions": {"key": "value"},
|
||||
"context_tokens": 1024,
|
||||
}
|
||||
instance = AgentInstanceCreate(
|
||||
agent_type_id=valid_uuid,
|
||||
project_id=valid_uuid,
|
||||
short_term_memory=memory,
|
||||
)
|
||||
assert instance.short_term_memory == memory
|
||||
|
||||
|
||||
class TestAgentInstanceStringFields:
|
||||
"""Tests for AgentInstance string field validation."""
|
||||
|
||||
def test_long_term_memory_ref_max_length(self, valid_uuid):
|
||||
"""Test long_term_memory_ref max length."""
|
||||
long_ref = "a" * 500 # Max length is 500
|
||||
|
||||
instance = AgentInstanceCreate(
|
||||
agent_type_id=valid_uuid,
|
||||
project_id=valid_uuid,
|
||||
long_term_memory_ref=long_ref,
|
||||
)
|
||||
assert instance.long_term_memory_ref == long_ref
|
||||
|
||||
def test_long_term_memory_ref_too_long(self, valid_uuid):
|
||||
"""Test that too long long_term_memory_ref raises ValidationError."""
|
||||
too_long = "a" * 501
|
||||
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
AgentInstanceCreate(
|
||||
agent_type_id=valid_uuid,
|
||||
project_id=valid_uuid,
|
||||
long_term_memory_ref=too_long,
|
||||
)
|
||||
|
||||
errors = exc_info.value.errors()
|
||||
assert any("long_term_memory_ref" in str(e).lower() for e in errors)
|
||||
|
||||
def test_session_id_max_length(self, valid_uuid):
|
||||
"""Test session_id max length."""
|
||||
long_session = "a" * 255 # Max length is 255
|
||||
|
||||
instance = AgentInstanceCreate(
|
||||
agent_type_id=valid_uuid,
|
||||
project_id=valid_uuid,
|
||||
session_id=long_session,
|
||||
)
|
||||
assert instance.session_id == long_session
|
||||
|
||||
def test_session_id_too_long(self, valid_uuid):
|
||||
"""Test that too long session_id raises ValidationError."""
|
||||
too_long = "a" * 256
|
||||
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
AgentInstanceCreate(
|
||||
agent_type_id=valid_uuid,
|
||||
project_id=valid_uuid,
|
||||
session_id=too_long,
|
||||
)
|
||||
|
||||
errors = exc_info.value.errors()
|
||||
assert any("session_id" in str(e).lower() for e in errors)
|
||||
Reference in New Issue
Block a user