# tests/schemas/syndarix/test_issue_schemas.py """ Tests for Issue schema validation. """ import uuid import pytest from pydantic import ValidationError from app.schemas.syndarix import ( IssueAssign, IssueCreate, IssuePriority, IssueStatus, IssueUpdate, SyncStatus, ) class TestIssueCreateValidation: """Tests for IssueCreate schema validation.""" def test_valid_issue_create(self, valid_issue_data): """Test creating issue with valid data.""" issue = IssueCreate(**valid_issue_data) assert issue.title == "Test Issue" assert issue.body == "Issue description" def test_issue_create_defaults(self, valid_issue_data): """Test that defaults are applied correctly.""" issue = IssueCreate(**valid_issue_data) assert issue.status == IssueStatus.OPEN assert issue.priority == IssuePriority.MEDIUM assert issue.labels == [] assert issue.story_points is None assert issue.assigned_agent_id is None assert issue.human_assignee is None assert issue.sprint_id is None def test_issue_create_with_all_fields(self, valid_uuid): """Test creating issue with all optional fields.""" agent_id = uuid.uuid4() sprint_id = uuid.uuid4() issue = IssueCreate( project_id=valid_uuid, title="Full Issue", body="Detailed body", status=IssueStatus.IN_PROGRESS, priority=IssuePriority.HIGH, labels=["bug", "security"], story_points=5, assigned_agent_id=agent_id, sprint_id=sprint_id, external_tracker_type="gitea", external_issue_id="gitea-123", remote_url="https://gitea.example.com/issues/123", external_issue_number=123, ) assert issue.status == IssueStatus.IN_PROGRESS assert issue.priority == IssuePriority.HIGH assert issue.labels == ["bug", "security"] assert issue.story_points == 5 assert issue.external_tracker_type == "gitea" def test_issue_create_title_empty_fails(self, valid_uuid): """Test that empty title raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: IssueCreate( project_id=valid_uuid, title="", ) errors = exc_info.value.errors() assert any("title" in str(e) for e in errors) def test_issue_create_title_whitespace_only_fails(self, valid_uuid): """Test that whitespace-only title raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: IssueCreate( project_id=valid_uuid, title=" ", ) errors = exc_info.value.errors() assert any("title" in str(e) for e in errors) def test_issue_create_title_stripped(self, valid_uuid): """Test that title is stripped.""" issue = IssueCreate( project_id=valid_uuid, title=" Padded Title ", ) assert issue.title == "Padded Title" def test_issue_create_project_id_required(self): """Test that project_id is required.""" with pytest.raises(ValidationError) as exc_info: IssueCreate(title="No Project Issue") errors = exc_info.value.errors() assert any("project_id" in str(e).lower() for e in errors) class TestIssueLabelsValidation: """Tests for Issue labels validation.""" def test_labels_normalized_lowercase(self, valid_uuid): """Test that labels are normalized to lowercase.""" issue = IssueCreate( project_id=valid_uuid, title="Test Issue", labels=["Bug", "SECURITY", "FrontEnd"], ) assert issue.labels == ["bug", "security", "frontend"] def test_labels_stripped(self, valid_uuid): """Test that labels are stripped.""" issue = IssueCreate( project_id=valid_uuid, title="Test Issue", labels=[" bug ", " security "], ) assert issue.labels == ["bug", "security"] def test_labels_empty_strings_removed(self, valid_uuid): """Test that empty label strings are removed.""" issue = IssueCreate( project_id=valid_uuid, title="Test Issue", labels=["bug", "", " ", "security"], ) assert issue.labels == ["bug", "security"] class TestIssueStoryPointsValidation: """Tests for Issue story_points validation.""" def test_story_points_valid_range(self, valid_uuid): """Test valid story_points values.""" for points in [0, 1, 5, 13, 21, 100]: issue = IssueCreate( project_id=valid_uuid, title="Test Issue", story_points=points, ) assert issue.story_points == points def test_story_points_negative_fails(self, valid_uuid): """Test that negative story_points raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: IssueCreate( project_id=valid_uuid, title="Test Issue", story_points=-1, ) errors = exc_info.value.errors() assert any("story_points" in str(e).lower() for e in errors) def test_story_points_over_100_fails(self, valid_uuid): """Test that story_points > 100 raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: IssueCreate( project_id=valid_uuid, title="Test Issue", story_points=101, ) errors = exc_info.value.errors() assert any("story_points" in str(e).lower() for e in errors) class TestIssueExternalTrackerValidation: """Tests for Issue external tracker validation.""" def test_valid_external_trackers(self, valid_uuid): """Test valid external tracker values.""" for tracker in ["gitea", "github", "gitlab"]: issue = IssueCreate( project_id=valid_uuid, title="Test Issue", external_tracker_type=tracker, external_issue_id="ext-123", ) assert issue.external_tracker_type == tracker def test_invalid_external_tracker(self, valid_uuid): """Test that invalid external tracker raises ValidationError.""" with pytest.raises(ValidationError): IssueCreate( project_id=valid_uuid, title="Test Issue", external_tracker_type="invalid", # type: ignore external_issue_id="ext-123", ) class TestIssueUpdateValidation: """Tests for IssueUpdate schema validation.""" def test_issue_update_partial(self): """Test updating only some fields.""" update = IssueUpdate( title="Updated Title", ) assert update.title == "Updated Title" assert update.body is None assert update.status is None def test_issue_update_all_fields(self): """Test updating all fields.""" agent_id = uuid.uuid4() sprint_id = uuid.uuid4() update = IssueUpdate( title="Updated Title", body="Updated body", status=IssueStatus.CLOSED, priority=IssuePriority.CRITICAL, labels=["updated"], assigned_agent_id=agent_id, human_assignee=None, sprint_id=sprint_id, story_points=8, sync_status=SyncStatus.PENDING, ) assert update.title == "Updated Title" assert update.status == IssueStatus.CLOSED assert update.priority == IssuePriority.CRITICAL def test_issue_update_empty_title_fails(self): """Test that empty title in update raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: IssueUpdate(title="") errors = exc_info.value.errors() assert any("title" in str(e) for e in errors) def test_issue_update_labels_normalized(self): """Test that labels are normalized in updates.""" update = IssueUpdate( labels=["Bug", "SECURITY"], ) assert update.labels == ["bug", "security"] class TestIssueAssignValidation: """Tests for IssueAssign schema validation.""" def test_assign_to_agent(self): """Test assigning to an agent.""" agent_id = uuid.uuid4() assign = IssueAssign(assigned_agent_id=agent_id) assert assign.assigned_agent_id == agent_id assert assign.human_assignee is None def test_assign_to_human(self): """Test assigning to a human.""" assign = IssueAssign(human_assignee="developer@example.com") assert assign.human_assignee == "developer@example.com" assert assign.assigned_agent_id is None def test_unassign(self): """Test unassigning (both None).""" assign = IssueAssign() assert assign.assigned_agent_id is None assert assign.human_assignee is None def test_assign_both_fails(self): """Test that assigning to both agent and human raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: IssueAssign( assigned_agent_id=uuid.uuid4(), human_assignee="developer@example.com", ) errors = exc_info.value.errors() # Check for the validation error message assert len(errors) > 0 class TestIssueEnums: """Tests for Issue enum validation.""" def test_valid_issue_statuses(self, valid_uuid): """Test all valid issue statuses.""" for status in IssueStatus: issue = IssueCreate( project_id=valid_uuid, title=f"Issue {status.value}", status=status, ) assert issue.status == status def test_invalid_issue_status(self, valid_uuid): """Test that invalid issue status raises ValidationError.""" with pytest.raises(ValidationError): IssueCreate( project_id=valid_uuid, title="Test Issue", status="invalid", # type: ignore ) def test_valid_issue_priorities(self, valid_uuid): """Test all valid issue priorities.""" for priority in IssuePriority: issue = IssueCreate( project_id=valid_uuid, title=f"Issue {priority.value}", priority=priority, ) assert issue.priority == priority def test_invalid_issue_priority(self, valid_uuid): """Test that invalid issue priority raises ValidationError.""" with pytest.raises(ValidationError): IssueCreate( project_id=valid_uuid, title="Test Issue", priority="invalid", # type: ignore ) def test_valid_sync_statuses(self): """Test all valid sync statuses in update.""" for status in SyncStatus: update = IssueUpdate(sync_status=status) assert update.sync_status == status