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:
262
backend/tests/models/syndarix/test_project.py
Normal file
262
backend/tests/models/syndarix/test_project.py
Normal file
@@ -0,0 +1,262 @@
|
||||
# tests/models/syndarix/test_project.py
|
||||
"""
|
||||
Unit tests for the Project model.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from app.models.syndarix import (
|
||||
AutonomyLevel,
|
||||
Project,
|
||||
ProjectStatus,
|
||||
)
|
||||
|
||||
|
||||
class TestProjectModel:
|
||||
"""Tests for Project model creation and fields."""
|
||||
|
||||
def test_create_project_with_required_fields(self, db_session):
|
||||
"""Test creating a project with only required fields."""
|
||||
project = Project(
|
||||
id=uuid.uuid4(),
|
||||
name="Test Project",
|
||||
slug="test-project",
|
||||
)
|
||||
db_session.add(project)
|
||||
db_session.commit()
|
||||
|
||||
retrieved = db_session.query(Project).filter_by(slug="test-project").first()
|
||||
|
||||
assert retrieved is not None
|
||||
assert retrieved.name == "Test Project"
|
||||
assert retrieved.slug == "test-project"
|
||||
assert retrieved.autonomy_level == AutonomyLevel.MILESTONE # Default
|
||||
assert retrieved.status == ProjectStatus.ACTIVE # Default
|
||||
assert retrieved.settings == {} # Default empty dict
|
||||
assert retrieved.description is None
|
||||
assert retrieved.owner_id is None
|
||||
|
||||
def test_create_project_with_all_fields(self, db_session):
|
||||
"""Test creating a project with all optional fields."""
|
||||
project_id = uuid.uuid4()
|
||||
owner_id = uuid.uuid4()
|
||||
|
||||
project = Project(
|
||||
id=project_id,
|
||||
name="Full Project",
|
||||
slug="full-project",
|
||||
description="A complete project with all fields",
|
||||
autonomy_level=AutonomyLevel.AUTONOMOUS,
|
||||
status=ProjectStatus.PAUSED,
|
||||
settings={"webhook_url": "https://example.com/webhook"},
|
||||
owner_id=owner_id,
|
||||
)
|
||||
db_session.add(project)
|
||||
db_session.commit()
|
||||
|
||||
retrieved = db_session.query(Project).filter_by(id=project_id).first()
|
||||
|
||||
assert retrieved.name == "Full Project"
|
||||
assert retrieved.slug == "full-project"
|
||||
assert retrieved.description == "A complete project with all fields"
|
||||
assert retrieved.autonomy_level == AutonomyLevel.AUTONOMOUS
|
||||
assert retrieved.status == ProjectStatus.PAUSED
|
||||
assert retrieved.settings == {"webhook_url": "https://example.com/webhook"}
|
||||
assert retrieved.owner_id == owner_id
|
||||
|
||||
def test_project_unique_slug_constraint(self, db_session):
|
||||
"""Test that projects cannot have duplicate slugs."""
|
||||
project1 = Project(
|
||||
id=uuid.uuid4(),
|
||||
name="Project One",
|
||||
slug="duplicate-slug",
|
||||
)
|
||||
db_session.add(project1)
|
||||
db_session.commit()
|
||||
|
||||
project2 = Project(
|
||||
id=uuid.uuid4(),
|
||||
name="Project Two",
|
||||
slug="duplicate-slug", # Same slug
|
||||
)
|
||||
db_session.add(project2)
|
||||
|
||||
with pytest.raises(IntegrityError):
|
||||
db_session.commit()
|
||||
|
||||
db_session.rollback()
|
||||
|
||||
def test_project_timestamps(self, db_session):
|
||||
"""Test that timestamps are automatically set."""
|
||||
project = Project(
|
||||
id=uuid.uuid4(),
|
||||
name="Timestamp Project",
|
||||
slug="timestamp-project",
|
||||
)
|
||||
db_session.add(project)
|
||||
db_session.commit()
|
||||
|
||||
retrieved = db_session.query(Project).filter_by(slug="timestamp-project").first()
|
||||
|
||||
assert isinstance(retrieved.created_at, datetime)
|
||||
assert isinstance(retrieved.updated_at, datetime)
|
||||
|
||||
def test_project_update(self, db_session):
|
||||
"""Test updating project fields."""
|
||||
project = Project(
|
||||
id=uuid.uuid4(),
|
||||
name="Original Name",
|
||||
slug="original-slug",
|
||||
status=ProjectStatus.ACTIVE,
|
||||
)
|
||||
db_session.add(project)
|
||||
db_session.commit()
|
||||
|
||||
original_created_at = project.created_at
|
||||
|
||||
# Update fields
|
||||
project.name = "Updated Name"
|
||||
project.status = ProjectStatus.COMPLETED
|
||||
project.settings = {"new_setting": "value"}
|
||||
db_session.commit()
|
||||
|
||||
retrieved = db_session.query(Project).filter_by(slug="original-slug").first()
|
||||
|
||||
assert retrieved.name == "Updated Name"
|
||||
assert retrieved.status == ProjectStatus.COMPLETED
|
||||
assert retrieved.settings == {"new_setting": "value"}
|
||||
assert retrieved.created_at == original_created_at
|
||||
assert retrieved.updated_at > original_created_at
|
||||
|
||||
def test_project_delete(self, db_session):
|
||||
"""Test deleting a project."""
|
||||
project_id = uuid.uuid4()
|
||||
project = Project(
|
||||
id=project_id,
|
||||
name="Delete Me",
|
||||
slug="delete-me",
|
||||
)
|
||||
db_session.add(project)
|
||||
db_session.commit()
|
||||
|
||||
db_session.delete(project)
|
||||
db_session.commit()
|
||||
|
||||
deleted = db_session.query(Project).filter_by(id=project_id).first()
|
||||
assert deleted is None
|
||||
|
||||
def test_project_string_representation(self, db_session):
|
||||
"""Test the string representation of a project."""
|
||||
project = Project(
|
||||
id=uuid.uuid4(),
|
||||
name="Repr Project",
|
||||
slug="repr-project",
|
||||
status=ProjectStatus.ACTIVE,
|
||||
)
|
||||
|
||||
assert str(project) == "<Project Repr Project (repr-project) status=active>"
|
||||
assert repr(project) == "<Project Repr Project (repr-project) status=active>"
|
||||
|
||||
|
||||
class TestProjectEnums:
|
||||
"""Tests for Project enum fields."""
|
||||
|
||||
def test_all_autonomy_levels(self, db_session):
|
||||
"""Test that all autonomy levels can be stored."""
|
||||
for level in AutonomyLevel:
|
||||
project = Project(
|
||||
id=uuid.uuid4(),
|
||||
name=f"Project {level.value}",
|
||||
slug=f"project-{level.value}",
|
||||
autonomy_level=level,
|
||||
)
|
||||
db_session.add(project)
|
||||
db_session.commit()
|
||||
|
||||
retrieved = db_session.query(Project).filter_by(slug=f"project-{level.value}").first()
|
||||
assert retrieved.autonomy_level == level
|
||||
|
||||
def test_all_project_statuses(self, db_session):
|
||||
"""Test that all project statuses can be stored."""
|
||||
for status in ProjectStatus:
|
||||
project = Project(
|
||||
id=uuid.uuid4(),
|
||||
name=f"Project {status.value}",
|
||||
slug=f"project-status-{status.value}",
|
||||
status=status,
|
||||
)
|
||||
db_session.add(project)
|
||||
db_session.commit()
|
||||
|
||||
retrieved = db_session.query(Project).filter_by(slug=f"project-status-{status.value}").first()
|
||||
assert retrieved.status == status
|
||||
|
||||
|
||||
class TestProjectSettings:
|
||||
"""Tests for Project JSON settings field."""
|
||||
|
||||
def test_complex_json_settings(self, db_session):
|
||||
"""Test storing complex JSON in settings."""
|
||||
complex_settings = {
|
||||
"mcp_servers": ["gitea", "slack", "file-system"],
|
||||
"webhook_urls": {
|
||||
"on_issue_created": "https://example.com/issue",
|
||||
"on_sprint_completed": "https://example.com/sprint",
|
||||
},
|
||||
"notification_settings": {
|
||||
"email": True,
|
||||
"slack_channel": "#syndarix-updates",
|
||||
},
|
||||
"tags": ["important", "client-a"],
|
||||
}
|
||||
|
||||
project = Project(
|
||||
id=uuid.uuid4(),
|
||||
name="Complex Settings Project",
|
||||
slug="complex-settings",
|
||||
settings=complex_settings,
|
||||
)
|
||||
db_session.add(project)
|
||||
db_session.commit()
|
||||
|
||||
retrieved = db_session.query(Project).filter_by(slug="complex-settings").first()
|
||||
|
||||
assert retrieved.settings == complex_settings
|
||||
assert retrieved.settings["mcp_servers"] == ["gitea", "slack", "file-system"]
|
||||
assert retrieved.settings["webhook_urls"]["on_issue_created"] == "https://example.com/issue"
|
||||
assert "important" in retrieved.settings["tags"]
|
||||
|
||||
def test_empty_settings(self, db_session):
|
||||
"""Test that empty settings defaults correctly."""
|
||||
project = Project(
|
||||
id=uuid.uuid4(),
|
||||
name="Empty Settings",
|
||||
slug="empty-settings",
|
||||
)
|
||||
db_session.add(project)
|
||||
db_session.commit()
|
||||
|
||||
retrieved = db_session.query(Project).filter_by(slug="empty-settings").first()
|
||||
assert retrieved.settings == {}
|
||||
|
||||
def test_update_settings(self, db_session):
|
||||
"""Test updating settings field."""
|
||||
project = Project(
|
||||
id=uuid.uuid4(),
|
||||
name="Update Settings",
|
||||
slug="update-settings",
|
||||
settings={"initial": "value"},
|
||||
)
|
||||
db_session.add(project)
|
||||
db_session.commit()
|
||||
|
||||
# Update settings
|
||||
project.settings = {"updated": "new_value", "additional": "data"}
|
||||
db_session.commit()
|
||||
|
||||
retrieved = db_session.query(Project).filter_by(slug="update-settings").first()
|
||||
assert retrieved.settings == {"updated": "new_value", "additional": "data"}
|
||||
Reference in New Issue
Block a user