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:
2025-12-30 02:07:27 +01:00
parent d6db6af964
commit 520a4d60fb
42 changed files with 9567 additions and 0 deletions

View 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)