# tests/schemas/syndarix/test_sprint_schemas.py """ Tests for Sprint schema validation. """ from datetime import date, timedelta import pytest from pydantic import ValidationError from app.schemas.syndarix import ( SprintCreate, SprintStatus, SprintUpdate, ) class TestSprintCreateValidation: """Tests for SprintCreate schema validation.""" def test_valid_sprint_create(self, valid_sprint_data): """Test creating sprint with valid data.""" sprint = SprintCreate(**valid_sprint_data) assert sprint.name == "Sprint 1" assert sprint.number == 1 assert sprint.start_date is not None assert sprint.end_date is not None def test_sprint_create_defaults(self, valid_sprint_data): """Test that defaults are applied correctly.""" sprint = SprintCreate(**valid_sprint_data) assert sprint.status == SprintStatus.PLANNED assert sprint.goal is None assert sprint.planned_points is None assert sprint.completed_points is None def test_sprint_create_with_all_fields(self, valid_uuid): """Test creating sprint with all optional fields.""" today = date.today() sprint = SprintCreate( project_id=valid_uuid, name="Full Sprint", number=5, goal="Complete all features", start_date=today, end_date=today + timedelta(days=14), status=SprintStatus.PLANNED, planned_points=21, completed_points=0, ) assert sprint.name == "Full Sprint" assert sprint.number == 5 assert sprint.goal == "Complete all features" assert sprint.planned_points == 21 def test_sprint_create_name_empty_fails(self, valid_uuid): """Test that empty name raises ValidationError.""" today = date.today() with pytest.raises(ValidationError) as exc_info: SprintCreate( project_id=valid_uuid, name="", number=1, start_date=today, end_date=today + timedelta(days=14), ) errors = exc_info.value.errors() assert any("name" in str(e) for e in errors) def test_sprint_create_name_whitespace_only_fails(self, valid_uuid): """Test that whitespace-only name raises ValidationError.""" today = date.today() with pytest.raises(ValidationError) as exc_info: SprintCreate( project_id=valid_uuid, name=" ", number=1, start_date=today, end_date=today + timedelta(days=14), ) errors = exc_info.value.errors() assert any("name" in str(e) for e in errors) def test_sprint_create_name_stripped(self, valid_uuid): """Test that name is stripped.""" today = date.today() sprint = SprintCreate( project_id=valid_uuid, name=" Padded Sprint Name ", number=1, start_date=today, end_date=today + timedelta(days=14), ) assert sprint.name == "Padded Sprint Name" def test_sprint_create_project_id_required(self): """Test that project_id is required.""" today = date.today() with pytest.raises(ValidationError) as exc_info: SprintCreate( name="Sprint 1", number=1, start_date=today, end_date=today + timedelta(days=14), ) errors = exc_info.value.errors() assert any("project_id" in str(e).lower() for e in errors) class TestSprintNumberValidation: """Tests for Sprint number validation.""" def test_sprint_number_valid(self, valid_uuid): """Test valid sprint numbers.""" today = date.today() for number in [1, 10, 100]: sprint = SprintCreate( project_id=valid_uuid, name=f"Sprint {number}", number=number, start_date=today, end_date=today + timedelta(days=14), ) assert sprint.number == number def test_sprint_number_zero_fails(self, valid_uuid): """Test that sprint number 0 raises ValidationError.""" today = date.today() with pytest.raises(ValidationError) as exc_info: SprintCreate( project_id=valid_uuid, name="Sprint Zero", number=0, start_date=today, end_date=today + timedelta(days=14), ) errors = exc_info.value.errors() assert any("number" in str(e).lower() for e in errors) def test_sprint_number_negative_fails(self, valid_uuid): """Test that negative sprint number raises ValidationError.""" today = date.today() with pytest.raises(ValidationError) as exc_info: SprintCreate( project_id=valid_uuid, name="Negative Sprint", number=-1, start_date=today, end_date=today + timedelta(days=14), ) errors = exc_info.value.errors() assert any("number" in str(e).lower() for e in errors) class TestSprintDateValidation: """Tests for Sprint date validation.""" def test_valid_date_range(self, valid_uuid): """Test valid date range (end > start).""" today = date.today() sprint = SprintCreate( project_id=valid_uuid, name="Sprint 1", number=1, start_date=today, end_date=today + timedelta(days=14), ) assert sprint.end_date > sprint.start_date def test_same_day_sprint(self, valid_uuid): """Test that same day sprint is valid.""" today = date.today() sprint = SprintCreate( project_id=valid_uuid, name="One Day Sprint", number=1, start_date=today, end_date=today, # Same day is allowed ) assert sprint.start_date == sprint.end_date def test_end_before_start_fails(self, valid_uuid): """Test that end date before start date raises ValidationError.""" today = date.today() with pytest.raises(ValidationError) as exc_info: SprintCreate( project_id=valid_uuid, name="Invalid Sprint", number=1, start_date=today, end_date=today - timedelta(days=1), # Before start ) errors = exc_info.value.errors() assert len(errors) > 0 class TestSprintPointsValidation: """Tests for Sprint points validation.""" def test_valid_planned_points(self, valid_uuid): """Test valid planned_points values.""" today = date.today() for points in [0, 1, 21, 100]: sprint = SprintCreate( project_id=valid_uuid, name=f"Sprint {points}", number=1, start_date=today, end_date=today + timedelta(days=14), planned_points=points, ) assert sprint.planned_points == points def test_planned_points_negative_fails(self, valid_uuid): """Test that negative planned_points raises ValidationError.""" today = date.today() with pytest.raises(ValidationError) as exc_info: SprintCreate( project_id=valid_uuid, name="Negative Points Sprint", number=1, start_date=today, end_date=today + timedelta(days=14), planned_points=-1, ) errors = exc_info.value.errors() assert any("planned_points" in str(e).lower() for e in errors) def test_valid_completed_points(self, valid_uuid): """Test valid completed_points values.""" today = date.today() for points in [0, 5, 21]: sprint = SprintCreate( project_id=valid_uuid, name=f"Sprint {points}", number=1, start_date=today, end_date=today + timedelta(days=14), completed_points=points, ) assert sprint.completed_points == points def test_completed_points_negative_fails(self, valid_uuid): """Test that negative completed_points raises ValidationError.""" today = date.today() with pytest.raises(ValidationError) as exc_info: SprintCreate( project_id=valid_uuid, name="Negative Completed Sprint", number=1, start_date=today, end_date=today + timedelta(days=14), completed_points=-1, ) errors = exc_info.value.errors() assert any("completed_points" in str(e).lower() for e in errors) class TestSprintUpdateValidation: """Tests for SprintUpdate schema validation.""" def test_sprint_update_partial(self): """Test updating only some fields.""" update = SprintUpdate( name="Updated Name", ) assert update.name == "Updated Name" assert update.goal is None assert update.start_date is None assert update.end_date is None def test_sprint_update_all_fields(self): """Test updating all fields.""" today = date.today() update = SprintUpdate( name="Updated Name", goal="Updated goal", start_date=today, end_date=today + timedelta(days=21), status=SprintStatus.ACTIVE, planned_points=34, completed_points=20, ) assert update.name == "Updated Name" assert update.goal == "Updated goal" assert update.status == SprintStatus.ACTIVE assert update.planned_points == 34 def test_sprint_update_empty_name_fails(self): """Test that empty name in update raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: SprintUpdate(name="") errors = exc_info.value.errors() assert any("name" in str(e) for e in errors) def test_sprint_update_name_stripped(self): """Test that name is stripped in updates.""" update = SprintUpdate(name=" Updated ") assert update.name == "Updated" class TestSprintStatusEnum: """Tests for SprintStatus enum validation.""" def test_valid_sprint_statuses(self, valid_uuid): """Test all valid sprint statuses.""" today = date.today() for status in SprintStatus: sprint = SprintCreate( project_id=valid_uuid, name=f"Sprint {status.value}", number=1, start_date=today, end_date=today + timedelta(days=14), status=status, ) assert sprint.status == status def test_invalid_sprint_status(self, valid_uuid): """Test that invalid sprint status raises ValidationError.""" today = date.today() with pytest.raises(ValidationError): SprintCreate( project_id=valid_uuid, name="Invalid Status Sprint", number=1, start_date=today, end_date=today + timedelta(days=14), status="invalid", # type: ignore )